Merge branch 'master' of https://github.com/micheloosterhof/kippo into logging-2

This commit is contained in:
Michel Oosterhof
2015-02-10 08:24:25 +00:00
11 changed files with 920 additions and 76 deletions

View File

@@ -24,7 +24,6 @@
* fixes for behavior with non-existent files (cd /test, cat /test/nonexistent, etc)
* fix for ability to ping/ssh non-existent IP address
* always send ssh exit-status 0 on exec and shell
* add 1st version of 'netstat'
* ls output is now alphabetically sorted
* banner_file is deprecated. honeyfs/etc/issue.net is default
* add 'dir' alias for 'ls'
@@ -32,4 +31,5 @@
* add 'users' aliased to 'whoami'
* add 'killall' and 'killall5' aliased to nop
* add 'poweroff' 'halt' and 'reboot' aliases for shutdown
* add 'which' and environment passing to commands
* add environment passing to commands
* added 'which', 'netstat' and 'gcc' from kippo-extra

View File

@@ -4,15 +4,6 @@ Kippo is a medium interaction SSH honeypot designed to log brute force attacks a
Kippo is inspired, but not based on [Kojoney](http://kojoney.sourceforge.net/).
## Demo
Some interesting logs from a live Kippo installation below (viewable within a web browser with the help of Ajaxterm). Note that some commands may have been improved since these logs were recorded.
* [2009-11-22](http://kippo.rpg.fi/playlog/?l=20091122-075013-5055.log)
* [2009-11-23](http://kippo.rpg.fi/playlog/?l=20091123-003854-3359.log)
* [2009-11-23](http://kippo.rpg.fi/playlog/?l=20091123-012814-626.log)
* [2010-03-16](http://kippo.rpg.fi/playlog/?l=20100316-233121-1847.log)
## Features
Some interesting features:
@@ -20,7 +11,6 @@ Some interesting features:
* Possibility of adding fake file contents so the attacker can 'cat' files such as /etc/passwd. Only minimal file contents are included
* Session logs stored in an [UML Compatible](http://user-mode-linux.sourceforge.net/) format for easy replay with original timings
* Just like Kojoney, Kippo saves files downloaded with wget for later inspection
* Trickery; ssh pretends to connect somewhere, exit doesn't really exit, etc
## Requirements

View File

@@ -1 +1,8 @@
root:0:123456
# This file contains user authorizations.
# To allow all passwords, use '*'
# To deny specific passwords, user '!password'
# The file is processed linearly, denials need to happen before allows.
# Default config allows all root passwords except 'root' and '123456'
root:0:!root
root:0:!123456
root:0:*

View File

@@ -18,4 +18,6 @@ __all__ = [
'malware',
'netstat',
'which',
'gcc',
'iptables'
]

274
kippo/commands/gcc.py Normal file
View File

@@ -0,0 +1,274 @@
# Copyright (c) 2013 Bas Stottelaar <basstottelaar [AT] gmail [DOT] com>
import time
import re
import getopt
import random
from twisted.internet import reactor
from kippo.core.honeypot import HoneyPotCommand
commands = {}
class command_gcc(HoneyPotCommand):
# Name of program. Under OSX, you might consider i686-apple-darwin11-llvm-gcc-X.X
APP_NAME = "gcc"
# GCC verson, used in help, version and the commandline name gcc-X.X
APP_VERSION = (4, 7, 2)
# Random binary data, which looks awesome. You could change this to whatever you want, but this
# data will be put in the actual file and thus exposed to our hacker when he\she cats the file.
RANDOM_DATA = "\x6a\x00\x48\x89\xe5\x48\x83\xe4\xf0\x48\x8b\x7d\x08\x48\x8d\x75\x10\x89\xfa" \
"\x83\xc2\x01\xc1\xe2\x03\x48\x01\xf2\x48\x89\xd1\xeb\x04\x48\x83\xc1\x08\x48" \
"\x83\x39\x00\x75\xf6\x48\x83\xc1\x08\xe8\x0c\x00\x00\x00\x89\xc7\xe8\xb9\x00" \
"\x00\x00\xf4\x90\x90\x90\x90\x55\x48\x89\xe5\x48\x83\xec\x40\x89\x7d\xfc\x48" \
"\x89\x75\xf0\x48\x8b\x45\xf0\x48\x8b\x00\x48\x83\xf8\x00\x75\x0c\xb8\x00\x00" \
"\x00\x00\x89\xc7\xe8\x8c\x00\x00\x00\x48\x8b\x45\xf0\x48\x8b\x40\x08\x30\xc9" \
"\x48\x89\xc7\x88\xc8\xe8\x7e\x00\x00\x00\x89\xc1\x89\x4d\xdc\x48\x8d\x0d\xd8" \
"\x01\x00\x00\x48\x89\xcf\x48\x89\x4d\xd0\xe8\x72\x00\x00\x00\x8b\x4d\xdc\x30" \
"\xd2\x48\x8d\x3d\xa4\x00\x00\x00\x89\xce\x88\x55\xcf\x48\x89\xc2\x8a\x45\xcf" \
"\xe8\x53\x00\x00\x00\x8b\x45\xdc\x88\x05\xc3\x01\x00\x00\x8b\x45\xdc\xc1\xe8" \
"\x08\x88\x05\xb8\x01\x00\x00\x8b\x45\xdc\xc1\xe8\x10\x88\x05\xad\x01\x00\x00" \
"\x8b\x45\xdc\xc1\xe8\x18\x88\x05\xa2\x01\x00\x00\x48\x8b\x45\xd0\x48\x89\x45" \
"\xe0\x48\x8b\x45\xe0\xff\xd0\x8b\x45\xec\x48\x83\xc4\x40\x5d\xc3\xff\x25\x3e" \
"\x01\x00\x00\xff\x25\x40\x01\x00\x00\xff\x25\x42\x01\x00\x00\xff\x25\x44\x01" \
"\x00\x00\x4c\x8d\x1d\x1d\x01\x00\x00\x41\x53\xff\x25\x0d\x01\x00\x00\x90\x68" \
"\x00\x00\x00\x00\xe9\xe6\xff\xff\xff\x68\x0c\x00\x00\x00\xe9\xdc\xff\xff\xff" \
"\x68\x1d\x00\x00\x00\xe9\xd2\xff\xff\xff\x68\x2b\x00\x00\x00\xe9\xc8\xff\xff" \
"\xff\x01\x00\x00\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x00\x00\x00\x00" \
"\x00\x00\x1c\x00\x00\x00\x02\x00\x00\x00\x00\x0e\x00\x00\x34\x00\x00\x00\x34" \
"\x00\x00\x00\xf5\x0e\x00\x00\x00\x00\x00\x00\x34\x00\x00\x00\x03\x00\x00\x00" \
"\x0c\x00\x02\x00\x14\x00\x02\x00\x00\x00\x00\x01\x40\x00\x00\x00\x00\x00\x00" \
"\x01\x00\x00\x00"
def start(self):
"""
Parse as much as possible from a GCC syntax and generate the output
that is requested. The file that is generated can be read (and will)
output garbage from an actual file, but when executed, it will generate
a segmentation fault.
The input files are expected to exists, but can be empty.
Verified syntaxes, including non-existing files:
* gcc test.c
* gcc test.c -o program
* gcc test1.c test2.c
* gcc test1.c test2.c -o program
* gcc test.c -o program -lm
* gcc -g test.c -o program -lm
* gcc test.c -DF_CPU=16000000 -I../etc -o program
* gcc test.c -O2 -o optimized_program
* gcc test.c -Wstrict-overflow=n -o overflowable_program
Others:
* gcc
* gcc -h
* gcc -v
* gcc --help
* gcc --version
"""
output_file = None
input_files = 0
complete = True
# Parse options or display no files
try:
opts, args = getopt.gnu_getopt(self.args, 'ESchvgo:x:l:I:W:D:X:O:', ['help', 'version', 'param'])
except getopt.GetoptError as err:
self.no_files()
return
# Parse options
for o, a in opts:
if o in ("-v"):
self.version(short=False)
return
elif o in ("--version"):
self.version(short=True)
return
elif o in ("-h"):
self.arg_missing("-h")
return
elif o in ("--help"):
self.help()
return
elif o in ("-o"):
if len(a) == 0:
self.arg_missing("-o")
else:
output_file = a
# Check for *.c or *.cpp files
for value in args:
if '.c' in value.lower():
sourcefile = self.fs.resolve_path(value, self.honeypot.cwd)
if self.fs.exists(sourcefile):
input_files = input_files + 1
else:
self.writeln("%s: %s: No such file or directory" % (command_gcc.APP_NAME, value))
complete = False
# To generate, or not
if input_files > 0 and complete:
func = lambda: self.generate_file(output_file if output_file else 'a.out')
timeout = 0.1 + random.random()
# Schedule call to make it more time consuming and real
self.scheduled = reactor.callLater(timeout, func)
else:
self.no_files()
def ctrl_c(self):
""" Make sure the scheduled call will be canceled """
if getattr(self, 'scheduled', False):
self.scheduled.cancel()
def no_files(self):
""" Notify user there are no input files, and exit """
self.writeln( """gcc: fatal error: no input files
compilation terminated.""" )
self.exit()
def version(self, short):
""" Print long or short version, and exit """
# Generate version number
version = '.'.join([ str(v) for v in command_gcc.APP_VERSION[:3] ])
version_short = '.'.join([ str(v) for v in command_gcc.APP_VERSION[:2] ])
if short:
data = ( """%s (Debian %s-8) %s
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.""", (command_gcc.APP_NAME, version, version) )
else:
data = ( """Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.7/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion=\'Debian %s-5\' --with-bugurl=file:///usr/share/doc/gcc-%s/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-%s --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/%s --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --with-arch-32=i586 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version %s (Debian %s-5)""" % (version, version_short, version_short, version_short, version, version))
# Write
self.writeln(data)
self.exit()
def generate_file(self, outfile):
data = ""
# TODO: make sure it is written to temp file, not downloads
safeoutfile = '%s/%s_%s' % \
(self.honeypot.env.cfg.get('honeypot', 'download_path'),
time.strftime('%Y%m%d%H%M%S'),
re.sub('[^A-Za-z0-9]', '_', outfile))
# Data contains random garbage from an actual file, so when
# catting the file, you'll see some 'real' compiled data
for i in range(random.randint(3, 15)):
if random.randint(1, 3) == 1:
data = data + command_gcc.RANDOM_DATA[::-1]
else:
data = data + command_gcc.RANDOM_DATA
# Write random data
with open(safeoutfile, 'wb') as f: f.write(data)
# Output file
outfile = self.fs.resolve_path(outfile, self.honeypot.cwd)
# Create file for the honeypot
self.fs.mkfile(outfile, 0, 0, len(data), 33188)
self.fs.update_realfile(self.fs.getfile(outfile), safeoutfile)
# Segfault command
class segfault_command(HoneyPotCommand):
def call(self):
self.write("Segmentation fault\n")
# Trick the 'new compiled file' as an segfault
self.honeypot.commands[outfile] = segfault_command
# Done
self.exit()
def arg_missing(self, arg):
""" Print missing argument message, and exit """
self.writeln("%s: argument to '%s' is missing" % (command_gcc.APP_NAME, arg))
self.exit()
def help(self):
""" Print help info, and exit """
version = '.'.join([ str(v) for v in command_gcc.APP_VERSION[:2] ])
self.writeln( """Usage: gcc [options] file...
Options:
-pass-exit-codes Exit with highest error code from a phase
--help Display this information
--target-help Display target specific command line options
--help={common|optimizers|params|target|warnings|[^]{joined|separate|undocumented}}[,...]
Display specific types of command line options
(Use '-v --help' to display command line options of sub-processes)
--version Display compiler version information
-dumpspecs Display all of the built in spec strings
-dumpversion Display the version of the compiler
-dumpmachine Display the compiler's target processor
-print-search-dirs Display the directories in the compiler's search path
-print-libgcc-file-name Display the name of the compiler's companion library
-print-file-name=<lib> Display the full path to library <lib>
-print-prog-name=<prog> Display the full path to compiler component <prog>
-print-multiarch Display the target's normalized GNU triplet, used as
a component in the library path
-print-multi-directory Display the root directory for versions of libgcc
-print-multi-lib Display the mapping between command line options and
multiple library search directories
-print-multi-os-directory Display the relative path to OS libraries
-print-sysroot Display the target libraries directory
-print-sysroot-headers-suffix Display the sysroot suffix used to find headers
-Wa,<options> Pass comma-separated <options> on to the assembler
-Wp,<options> Pass comma-separated <options> on to the preprocessor
-Wl,<options> Pass comma-separated <options> on to the linker
-Xassembler <arg> Pass <arg> on to the assembler
-Xpreprocessor <arg> Pass <arg> on to the preprocessor
-Xlinker <arg> Pass <arg> on to the linker
-save-temps Do not delete intermediate files
-save-temps=<arg> Do not delete intermediate files
-no-canonical-prefixes Do not canonicalize paths when building relative
prefixes to other gcc components
-pipe Use pipes rather than intermediate files
-time Time the execution of each subprocess
-specs=<file> Override built-in specs with the contents of <file>
-std=<standard> Assume that the input sources are for <standard>
--sysroot=<directory> Use <directory> as the root directory for headers
and libraries
-B <directory> Add <directory> to the compiler's search paths
-v Display the programs invoked by the compiler
-### Like -v but options quoted and commands not executed
-E Preprocess only; do not compile, assemble or link
-S Compile only; do not assemble or link
-c Compile and assemble, but do not link
-o <file> Place the output into <file>
-pie Create a position independent executable
-shared Create a shared library
-x <language> Specify the language of the following input files
Permissible languages include: c c++ assembler none
'none' means revert to the default behavior of
guessing the language based on the file's extension
Options starting with -g, -f, -m, -O, -W, or --param are automatically
passed on to the various sub-processes invoked by gcc. In order to pass
other options on to these processes the -W<letter> options must be used.
For bug reporting instructions, please see:
<file:///usr/share/doc/gcc-4.7/README.Bugs>.""")
self.exit()
# Definitions
commands['/usr/bin/gcc'] = command_gcc
commands['/usr/bin/gcc-%s' % ('.'.join([ str(v) for v in command_gcc.APP_VERSION[:2] ]))] = command_gcc

414
kippo/commands/iptables.py Normal file
View File

@@ -0,0 +1,414 @@
# Copyright (c) 2013 Bas Stottelaar <basstottelaar [AT] gmail [DOT] com>
import optparse
from twisted.internet import reactor
from kippo.core.honeypot import HoneyPotCommand
commands = {}
class OptionParsingError(RuntimeError):
def __init__(self, msg):
self.msg = msg
class OptionParsingExit(Exception):
def __init__(self, status, msg):
self.msg = msg
self.status = status
class ModifiedOptionParser(optparse.OptionParser):
def error(self, msg):
raise OptionParsingError(msg)
def exit(self, status=0, msg=None):
raise OptionParsingExit(status, msg)
class command_iptables(HoneyPotCommand):
# Do not resolve args
resolve_args = False
# iptables app name
APP_NAME = "iptables"
# iptables app version, used in help messages etc.
APP_VERSION = "v1.4.14"
# Default iptable table
DEFAULT_TABLE = "filter"
def user_is_root(self):
return self.honeypot.user.username == 'root'
def start(self):
"""
Emulate iptables commands, including permission checking.
Verified examples:
* iptables -A OUTPUT -o eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
* iptables -A INPUT -i eth0 -p tcp -s "127.0.0.1" -j DROP
Others:
* iptables
* iptables [[-t | --table] <name>] [-h | --help]
* iptables [[-t | --table] <name>] [-v | --version]
* iptables [[-t | --table] <name>] [-F | --flush] <chain>
* iptables [[-t | --table] <name>] [-L | --list] <chain>
* iptables [[-t | --table] <name>] [-S | --list-rules] <chain>
* iptables --this-is-invalid
"""
# In case of no arguments
if len(self.args) == 0:
self.no_command()
return
# Utils
def optional_arg(arg_default):
def func(option,opt_str,value,parser):
if parser.rargs and not parser.rargs[0].startswith('-'):
val=parser.rargs[0]
parser.rargs.pop(0)
else:
val=arg_default
setattr(parser.values,option.dest,val)
return func
# Initialize options
parser = ModifiedOptionParser(add_help_option=False)
parser.add_option("-h", "--help", dest="help", action="store_true")
parser.add_option("-V", "--version", dest="version", action="store_true")
parser.add_option("-v", "--verbose", dest="verbose", action="store_true")
parser.add_option("-x", "--exact", dest="exact", action="store_true")
parser.add_option("--line-numbers", dest="line_numbers", action="store_true")
parser.add_option("--modprobe", dest="modprobe", action="store")
parser.add_option("-t", "--table", dest="table", action="store", default=command_iptables.DEFAULT_TABLE)
parser.add_option("-F", "--flush", dest="flush", action="callback", callback=optional_arg(True))
parser.add_option("-Z", "--zero", dest="zero", action="callback", callback=optional_arg(True))
parser.add_option("-S", "--list-rules", dest="list_rules", action="callback", callback=optional_arg(True))
parser.add_option("-L", "--list", dest="list", action="callback", callback=optional_arg(True))
parser.add_option("-A", "--append", dest="append", action="store")
parser.add_option("-D", "--delete", dest="delete", action="store")
parser.add_option("-I", "--insert", dest="insert", action="store")
parser.add_option("-R", "--replace", dest="replace", action="store")
parser.add_option("-N", "--new-chain", dest="new_chain", action="store")
parser.add_option("-X", "--delete-chain", dest="delete_chain", action="store")
parser.add_option("-P", "--policy", dest="policy", action="store")
parser.add_option("-E", "--rename-chain", dest="rename_chain", action="store")
parser.add_option("-p", "--protocol", dest="protocol", action="store")
parser.add_option("-s", "--source", dest="source", action="store")
parser.add_option("-d", "--destination", dest="destination", action="store")
parser.add_option("-j", "--jump", dest="jump", action="store")
parser.add_option("-g", "--goto", dest="goto", action="store")
parser.add_option("-i", "--in-interface", dest="in_interface", action="store")
parser.add_option("-o", "--out-interface", dest="out_interface", action="store")
parser.add_option("-f", "--fragment", dest="fragment", action="store_true")
parser.add_option("-c", "--set-counters", dest="set_counters", action="store")
parser.add_option("-m", "--match", dest="match", action="store")
parser.add_option("--sport", "--source-ports", dest="source_ports", action="store")
parser.add_option("--dport", "--destination-ports", dest="dest_ports", action="store")
parser.add_option("--ports", dest="ports", action="store")
parser.add_option("--state", dest="state", action="store")
# Parse options or display no files
try:
(opts, args) = parser.parse_args(list(self.args))
except OptionParsingError, e:
self.bad_argument(self.args[0])
return
except OptionParsingExit, e:
self.unknown_option(e)
return
# Initialize table
if not self.setup_table(opts.table):
return
# Parse options
if opts.help:
self.show_help()
return
elif opts.version:
self.show_version()
return
elif opts.flush:
self.flush("" if opts.flush == True else opts.flush)
return
elif opts.list:
self.list("" if opts.list == True else opts.list)
return
elif opts.list_rules:
self.list_rules("" if opts.list_rules == True else opts.list_rules)
return
# Done
self.exit()
def setup_table(self, table):
"""
Called during startup to make sure the current environment has some
fake rules in memory.
"""
# Create fresh tables on start
if not hasattr(self.honeypot.env, 'iptables'):
setattr(self.honeypot.env, 'iptables', {
"raw": {
"PREROUTING": [],
"OUTPUT": []
},
"filter": {
"INPUT": [
('ACCEPT', 'tcp', '--', 'anywhere', 'anywhere', 'tcp', 'dpt:ssh'),
('DROP', 'all', '--', 'anywhere', 'anywhere', '', '')
],
"FORWARD": [],
"OUTPUT": []
},
"mangle": {
"PREROUTING": [],
"INPUT": [],
"FORWARD": [],
"OUTPUT": [],
"POSTROUTING": []
},
"nat": {
"PREROUTING": [],
"OUTPUT": []
}
})
# Get the tables
self.tables = getattr(self.honeypot.env, 'iptables')
# Verify selected table
if not self.is_valid_table(table):
return False
# Set table
self.current_table = self.tables[table]
# Done
return True
def is_valid_table(self, table):
if self.user_is_root():
# Verify table existence
if not table in self.tables.iterkeys():
self.writeln( """%s: can\'t initialize iptables table \'%s\': Table does not exist (do you need to insmod?)
Perhaps iptables or your kernel needs to be upgraded.""" % (command_iptables.APP_NAME, table) )
self.exit()
else:
# Exists
return True
else:
self.no_permission()
# Failed
return False
def is_valid_chain(self, chain):
# Verify chain existence. Requires valid table first
if not chain in self.current_table.iterkeys():
self.writeln("%s: No chain/target/match by that name." % command_iptables.APP_NAME)
self.exit()
return False
# Exists
return True
def show_version(self):
""" Show version and exit """
self.writeln('%s %s' % (command_iptables.APP_NAME, command_iptables.APP_VERSION))
self.exit()
def show_help(self):
""" Show help and exit """
self.writeln( """%s %s'
Usage: iptables -[AD] chain rule-specification [options]
iptables -I chain [rulenum] rule-specification [options]
iptables -R chain rulenum rule-specification [options]
iptables -D chain rulenum [options]
iptables -[LS] [chain [rulenum]] [options]
iptables -[FZ] [chain] [options]
iptables -[NX] chain
iptables -E old-chain-name new-chain-name
iptables -P chain target [options]
iptables -h (print this help information)
Commands:
Either long or short options are allowed.
--append -A chain Append to chain
--delete -D chain Delete matching rule from chain
--delete -D chain rulenum
Delete rule rulenum (1 = first) from chain
--insert -I chain [rulenum]
Insert in chain as rulenum (default 1=first)
--replace -R chain rulenum
Replace rule rulenum (1 = first) in chain
--list -L [chain [rulenum]]
List the rules in a chain or all chains
--list-rules -S [chain [rulenum]]
Print the rules in a chain or all chains
--flush -F [chain] Delete all rules in chain or all chains
--zero -Z [chain [rulenum]]
Zero counters in chain or all chains
--new -N chain Create a new user-defined chain
--delete-chain
-X [chain] Delete a user-defined chain
--policy -P chain target
Change policy on chain to target
--rename-chain
-E old-chain new-chain
Change chain name, (moving any references)
Options:
[!] --proto -p proto protocol: by number or name, eg. \'tcp\'
[!] --source -s address[/mask][...]
source specification
[!] --destination -d address[/mask][...]
destination specification
[!] --in-interface -i input name[+]
network interface name ([+] for wildcard)
--jump -j target
target for rule (may load target extension)
--goto -g chain
jump to chain with no return
--match -m match
extended match (may load extension)
--numeric -n numeric output of addresses and ports
[!] --out-interface -o output name[+]
network interface name ([+] for wildcard)
--table -t table table to manipulate (default: \'filter\')
--verbose -v verbose mode
--line-numbers print line numbers when listing
--exact -x expand numbers (display exact values)
[!] --fragment -f match second or further fragments only
--modprobe=<command> try to insert modules using this command
--set-counters PKTS BYTES set the counter during insert/append
[!] --version -V print package version.""" % (command_iptables.APP_NAME, command_iptables.APP_VERSION))
self.exit()
def list_rules(self, chain):
""" List current rules as commands"""
if self.user_is_root():
if len(chain) > 0:
print chain
# Check chain
if not self.is_valid_chain(chain):
return
chains = [chain]
else:
chains = self.current_table.iterkeys()
# Output buffer
output = []
for chain in chains:
output.append("-P %s ACCEPT" % chain)
# Done
self.writeln(output)
self.exit()
else:
self.no_permission()
def list(self, chain):
""" List current rules """
if self.user_is_root():
if len(chain) > 0:
print chain
# Check chain
if not self.is_valid_chain(chain):
return
chains = [chain]
else:
chains = self.current_table.iterkeys()
# Output buffer
output = []
for chain in chains:
# Chain table header
chain_output = [
"Chain %s (policy ACCEPT)" % chain,
"target prot opt source destination",
]
# Format the rules
for rule in self.current_table[chain]:
chain_output.append(
"%-10s %-4s %-3s %-20s %-20s %s %s" % rule,
)
# Create one string
output.append("\n".join(chain_output))
# Done
self.writeln("\n\n".join(output))
self.exit()
else:
self.no_permission()
def flush(self, chain):
""" Mark rules as flushed """
if self.user_is_root():
if len(chain) > 0:
# Check chain
if not self.is_valid_chain(chain):
return
chains = [chain]
else:
chains = self.current_table.iterkeys()
# Flush
for chain in chains:
self.current_table[chain] = []
self.exit()
else:
self.no_permission()
def no_permission(self):
self.writeln( """%s %s: can\'t initialize iptables table \'filter\': Permission denied (you must be root)
Perhaps iptables or your kernel needs to be upgraded."""
% (command_iptables.APP_NAME, command_iptables.APP_VERSION) )
self.exit()
def no_command(self):
""" Print no command message and exit """
self.writeln( """%s %s: no command specified'
Try `iptables -h\' or \'iptables --help\' for more information."""
% (command_iptables.APP_NAME, command_iptables.APP_VERSION) )
self.exit()
def unknown_option(self, option):
""" Print unknown option message and exit """
self.writeln( """%s %s: unknown option \'%s\''
Try `iptables -h\' or \'iptables --help\' for more information."""
% (command_iptables.APP_NAME, command_iptables.APP_VERSION, option) )
self.exit()
def bad_argument(self, argument):
""" Print bad argument and exit """
self.writeln( """Bad argument \'%s\'' % argument,
Try `iptables -h\' or \'iptables --help\' for more information."""
% argument )
self.exit()
# Definition
commands['/sbin/iptables'] = command_iptables

View File

@@ -41,6 +41,9 @@ class UserDB(object):
if not line:
continue
if line.startswith( '#' ):
continue
(login, uid_str, passwd) = line.split(':', 2)
uid = 0

View File

@@ -1,4 +1,4 @@
/* From https://github.com/threatstream/kippo/blob/master/kippo/dblog/hpfeeds.py */
## From https://github.com/threatstream/kippo/blob/master/kippo/dblog/hpfeeds.py
from kippo.core import dblog
from twisted.python import log

67
utils/elk/README.md Normal file
View File

@@ -0,0 +1,67 @@
# How to process Kippo output in an ELK stack
(Note: work in progress, instructions are not verified)
## Prerequisites
* Working Kippo installation
* Kippo JSON log file (enable database json in kippo.cfg)
## Installation
* Install logstash, elasticsearch and kibana
```
apt-get install logstash
apt-get install elasticsearch
````
* Install Kibana
This may be different depending on your operating system. Kibana will need additional components such as a web server
## ElasticSearch Configuration
TBD
## Logstash Configuration
* Download GeoIP data
```
wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
wget http://download.maxmind.com/download/geoip/database/asnum/GeoIPASNum.dat.gz
```
* Place these somewhere in your filesystem.
* Configure logstash
```
cp logstash-kippo.conf /etc/logstash/conf.d
```
* Make sure the configuration file is correct. Check the input section (path), filter (geoip databases) and output (elasticsearch hostname)
```
service logstash restart
```
* By default the logstash is creating debug logs in /tmp.
* To test whether logstash is working correctly, check the file in /tmp
```
tail /tmp/kippo-logstash.log
```
* To test whether data is loaded into ElasticSearch, run the following query:
```
http://<hostname>:9200/_search?q=kippo&size=5
```
* If this gives output, your data is correctly loaded into ElasticSearch

View File

@@ -18,8 +18,31 @@
]
},
"filter": {
"list": {},
"ids": []
"list": {
"0": {
"type": "terms",
"field": "_type",
"value": "kippo",
"mandate": "must",
"active": true,
"alias": "",
"id": 0
},
"1": {
"type": "time",
"field": "@timestamp",
"from": "now-30d",
"to": "now",
"mandate": "must",
"active": true,
"alias": "",
"id": 1
}
},
"ids": [
0,
1
]
}
},
"rows": [
@@ -30,6 +53,39 @@
"collapse": false,
"collapsable": true,
"panels": [
{
"error": false,
"span": 3,
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "sensor",
"exclude": [],
"missing": false,
"other": false,
"size": 5,
"order": "count",
"style": {
"font-size": "10pt"
},
"donut": false,
"tilt": false,
"labels": true,
"arrangement": "horizontal",
"chart": "table",
"counter_pos": "above",
"spyable": true,
"queries": {
"mode": "all",
"ids": [
0
]
},
"tmode": "terms",
"tstat": "total",
"valuefield": "",
"title": "Sensors"
},
{
"error": false,
"span": 3,
@@ -97,39 +153,6 @@
"tstat": "total",
"valuefield": "",
"title": "Successes"
},
{
"error": false,
"span": 3,
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "sensor",
"exclude": [],
"missing": false,
"other": false,
"size": 5,
"order": "count",
"style": {
"font-size": "10pt"
},
"donut": false,
"tilt": false,
"labels": true,
"arrangement": "horizontal",
"chart": "table",
"counter_pos": "above",
"spyable": true,
"queries": {
"mode": "all",
"ids": [
0
]
},
"tmode": "terms",
"tstat": "total",
"valuefield": "",
"title": "Sensors"
}
],
"notice": false
@@ -231,7 +254,7 @@
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "username",
"field": "username.raw",
"exclude": [],
"missing": false,
"other": false,
@@ -264,7 +287,7 @@
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "username",
"field": "username.raw",
"exclude": [],
"missing": false,
"other": false,
@@ -307,7 +330,7 @@
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "password",
"field": "password.raw",
"exclude": [],
"missing": false,
"other": false,
@@ -340,7 +363,7 @@
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "password",
"field": "password.raw",
"exclude": [],
"missing": false,
"other": false,
@@ -383,7 +406,7 @@
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "client",
"field": "client.raw",
"exclude": [],
"missing": false,
"other": false,
@@ -416,7 +439,7 @@
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "client",
"field": "client.raw",
"exclude": [],
"missing": false,
"other": false,
@@ -480,15 +503,23 @@
"error": false,
"span": 4,
"editable": true,
"type": "map",
"type": "terms",
"loadingEditor": false,
"map": "europe",
"colors": [
"#A0E2E2",
"#265656"
],
"size": 100,
"field": "geoip.country_name.raw",
"exclude": [],
"missing": false,
"other": true,
"size": 20,
"order": "count",
"style": {
"font-size": "10pt"
},
"donut": false,
"tilt": false,
"labels": true,
"arrangement": "horizontal",
"chart": "table",
"counter_pos": "above",
"spyable": true,
"queries": {
"mode": "all",
@@ -496,8 +527,10 @@
0
]
},
"title": "Attack map (Europe)",
"field": "country_code2"
"tmode": "terms",
"tstat": "count",
"valuefield": "",
"title": "Countries"
}
],
"notice": false
@@ -506,7 +539,7 @@
"title": "Events",
"height": "650px",
"editable": true,
"collapse": false,
"collapse": true,
"collapsable": true,
"panels": [
{
@@ -551,11 +584,54 @@
}
],
"notice": false
},
{
"title": "ASN",
"height": "150px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [
{
"error": false,
"span": 4,
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "geoip.asn.raw",
"exclude": [],
"missing": true,
"other": true,
"size": 20,
"order": "count",
"style": {
"font-size": "10pt"
},
"donut": false,
"tilt": false,
"labels": true,
"arrangement": "horizontal",
"chart": "table",
"counter_pos": "above",
"spyable": true,
"queries": {
"mode": "all",
"ids": [
0
]
},
"tmode": "terms",
"tstat": "total",
"valuefield": "",
"title": "ASN"
}
],
"notice": false
}
],
"editable": true,
"index": {
"interval": "none",
"interval": "day",
"pattern": "[logstash-]YYYY.MM.DD",
"default": "_all",
"warm_fields": false
@@ -624,8 +700,10 @@
"2h",
"1d"
],
"timefield": "timestamp",
"enable": true
"timefield": "@timestamp",
"enable": true,
"now": true,
"filter_id": 1
}
],
"refresh": false

View File

@@ -1,9 +1,13 @@
input {
# this is the actual live log file to monitor
file {
path => ["/home/michel/src/kippo-git/log/kippo.json", "/home/kippo/kippo-git/log/kippo.json"]
# path => ["/home/michel/src/kippo-git/log/kippo.json"]
codec => json
path => ["/home/kippo/kippo-git/log/kippo.json"]
codec => json_lines
type => "kippo"
}
# this is to send old logs to for reprocessing
tcp {
port => 3333
type => "kippo"
}
}
@@ -11,11 +15,13 @@ input {
filter {
if [type] == "kippo" {
date {
match => [ "timestamp", "ISO8601" ]
locale => "en"
json {
source => message
}
date {
match => [ "timestamp", "ISO8601" ]
}
if [src_ip] {
@@ -46,7 +52,10 @@ filter {
output {
if [type] == "kippo" {
elasticsearch { host => helium }
elasticsearch {
host => helium
protocol => http
}
file {
path => "/tmp/kippo-logstash.log"
codec => json