diff --git a/Makefile.in b/Makefile.in index 325e102c3..20f9dd666 100644 --- a/Makefile.in +++ b/Makefile.in @@ -52,6 +52,7 @@ STATIC = LDFLAGS = @LDFLAGS@ $(DBGFLAGS) $(STATIC) LIBS = @LIBNBASE_LIBS@ @LIBNSOCK_LIBS@ @LIBPCRE_LIBS@ @LIBPCAP_LIBS@ $(OPENSSL_LIBS) libnetutil/libnetutil.a @LIBDNET_LIBS@ @LIBLUA_LIBS@ @LIBLINEAR_LIBS@ @LIBS@ OPENSSL_LIBS = @OPENSSL_LIBS@ +LIBS += -lssh2 #Just for now # LIBS = -lefence @LIBS@ # LIBS = -lrmalloc @LIBS@ INSTALL = @INSTALL@ @@ -86,9 +87,9 @@ UNINSTALLZENMAP=@UNINSTALLZENMAP@ UNINSTALLNPING=@UNINSTALLNPING@ ifneq (@LIBLUA_LIBS@,) -NSE_SRC=nse_main.cc nse_utility.cc nse_nsock.cc nse_dnet.cc nse_fs.cc nse_nmaplib.cc nse_debug.cc nse_pcrelib.cc nse_binlib.cc nse_bit.cc nse_lpeg.cc -NSE_HDRS=nse_main.h nse_utility.h nse_nsock.h nse_dnet.h nse_fs.h nse_nmaplib.h nse_debug.h nse_pcrelib.h nse_binlib.h nse_bit.h nse_lpeg.h -NSE_OBJS=nse_main.o nse_utility.o nse_nsock.o nse_dnet.o nse_fs.o nse_nmaplib.o nse_debug.o nse_pcrelib.o nse_binlib.o nse_bit.o nse_lpeg.o +NSE_SRC=nse_main.cc nse_utility.cc nse_nsock.cc nse_dnet.cc nse_fs.cc nse_nmaplib.cc nse_debug.cc nse_pcrelib.cc nse_binlib.cc nse_bit.cc nse_lpeg.cc nse_libssh2.cc +NSE_HDRS=nse_main.h nse_utility.h nse_nsock.h nse_dnet.h nse_fs.h nse_nmaplib.h nse_debug.h nse_pcrelib.h nse_binlib.h nse_bit.h nse_lpeg.h nse_libssh2.h +NSE_OBJS=nse_main.o nse_utility.o nse_nsock.o nse_dnet.o nse_fs.o nse_nmaplib.o nse_debug.o nse_pcrelib.o nse_binlib.o nse_bit.o nse_lpeg.o nse_libssh2.o ifneq (@OPENSSL_LIBS@,) NSE_SRC+=nse_openssl.cc nse_ssl_cert.cc NSE_HDRS+=nse_openssl.h nse_ssl_cert.h diff --git a/aclocal.m4 b/aclocal.m4 index 73d548ab8..1972f4079 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -1,8 +1,7 @@ -# generated automatically by aclocal 1.11.6 -*- Autoconf -*- +# generated automatically by aclocal 1.14.1 -*- Autoconf -*- + +# Copyright (C) 1996-2013 Free Software Foundation, Inc. -# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, -# 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software Foundation, -# Inc. # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. @@ -12,8 +11,9 @@ # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. +m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])]) # nls.m4 serial 5 (gettext-0.18) -dnl Copyright (C) 1995-2003, 2005-2006, 2008-2010 Free Software Foundation, +dnl Copyright (C) 1995-2003, 2005-2006, 2008-2013 Free Software Foundation, dnl Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -45,14 +45,12 @@ AC_DEFUN([AM_NLS], AC_SUBST([USE_NLS]) ]) -# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2008, 2009, -# 2011 Free Software Foundation, Inc. +# Copyright (C) 1999-2013 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. -# serial 2 # AM_PATH_PYTHON([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # --------------------------------------------------------------------------- @@ -81,7 +79,7 @@ AC_DEFUN([AM_PATH_PYTHON], dnl Find a Python interpreter. Python versions prior to 2.0 are not dnl supported. (2.0 was released on October 16, 2000). m4_define_default([_AM_PYTHON_INTERPRETER_LIST], -[python python2 python3 python3.2 python3.1 python3.0 python2.7 dnl +[python python2 python3 python3.3 python3.2 python3.1 python3.0 python2.7 dnl python2.6 python2.5 python2.4 python2.3 python2.2 python2.1 python2.0]) AC_ARG_VAR([PYTHON], [the Python interpreter]) @@ -97,10 +95,11 @@ AC_DEFUN([AM_PATH_PYTHON], dnl A version check is needed. if test -n "$PYTHON"; then # If the user set $PYTHON, use it and don't search something else. - AC_MSG_CHECKING([whether $PYTHON version >= $1]) + AC_MSG_CHECKING([whether $PYTHON version is >= $1]) AM_PYTHON_CHECK_VERSION([$PYTHON], [$1], - [AC_MSG_RESULT(yes)], - [AC_MSG_ERROR(too old)]) + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no]) + AC_MSG_ERROR([Python interpreter is too old])]) am_display_PYTHON=$PYTHON else # Otherwise, try each interpreter until we find one that satisfies @@ -149,6 +148,25 @@ AC_DEFUN([AM_PATH_PYTHON], [am_cv_python_platform=`$PYTHON -c "import sys; sys.stdout.write(sys.platform)"`]) AC_SUBST([PYTHON_PLATFORM], [$am_cv_python_platform]) + # Just factor out some code duplication. + am_python_setup_sysconfig="\ +import sys +# Prefer sysconfig over distutils.sysconfig, for better compatibility +# with python 3.x. See automake bug#10227. +try: + import sysconfig +except ImportError: + can_use_sysconfig = 0 +else: + can_use_sysconfig = 1 +# Can't use sysconfig in CPython 2.7, since it's broken in virtualenvs: +# +try: + from platform import python_implementation + if python_implementation() == 'CPython' and sys.version[[:3]] == '2.7': + can_use_sysconfig = 0 +except ImportError: + pass" dnl Set up 4 directories: @@ -165,7 +183,14 @@ AC_DEFUN([AM_PATH_PYTHON], else am_py_prefix=$prefix fi - am_cv_python_pythondir=`$PYTHON -c "import sys; from distutils import sysconfig; sys.stdout.write(sysconfig.get_python_lib(0,0,prefix='$am_py_prefix'))" 2>/dev/null` + am_cv_python_pythondir=`$PYTHON -c " +$am_python_setup_sysconfig +if can_use_sysconfig: + sitedir = sysconfig.get_path('purelib', vars={'base':'$am_py_prefix'}) +else: + from distutils import sysconfig + sitedir = sysconfig.get_python_lib(0, 0, prefix='$am_py_prefix') +sys.stdout.write(sitedir)"` case $am_cv_python_pythondir in $am_py_prefix*) am__strip_prefix=`echo "$am_py_prefix" | sed 's|.|.|g'` @@ -200,7 +225,14 @@ AC_DEFUN([AM_PATH_PYTHON], else am_py_exec_prefix=$exec_prefix fi - am_cv_python_pyexecdir=`$PYTHON -c "import sys; from distutils import sysconfig; sys.stdout.write(sysconfig.get_python_lib(1,0,prefix='$am_py_exec_prefix'))" 2>/dev/null` + am_cv_python_pyexecdir=`$PYTHON -c " +$am_python_setup_sysconfig +if can_use_sysconfig: + sitedir = sysconfig.get_path('platlib', vars={'platbase':'$am_py_prefix'}) +else: + from distutils import sysconfig + sitedir = sysconfig.get_python_lib(1, 0, prefix='$am_py_prefix') +sys.stdout.write(sitedir)"` case $am_cv_python_pyexecdir in $am_py_exec_prefix*) am__strip_prefix=`echo "$am_py_exec_prefix" | sed 's|.|.|g'` @@ -248,14 +280,12 @@ for i in list(range(0, 4)): minverhex = (minverhex << 8) + minver[[i]] sys.exit(sys.hexversion < minverhex)" AS_IF([AM_RUN_LOG([$1 -c "$prog"])], [$3], [$4])]) -# Copyright (C) 2001, 2003, 2005, 2011 Free Software Foundation, Inc. +# Copyright (C) 2001-2013 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. -# serial 1 - # AM_RUN_LOG(COMMAND) # ------------------- # Run COMMAND, save the exit status in ac_status, and log it. diff --git a/configure b/configure index 2c82adbad..19863ad6f 100755 --- a/configure +++ b/configure @@ -661,6 +661,7 @@ DNET_BUILD DNET_DEPENDS LIBDNETDIR LIBDNET_LIBS +LIBSSH2_LIBS PCRE_DIST_CLEAN PCRE_CLEAN PCRE_BUILD @@ -781,6 +782,7 @@ with_nping with_openssl with_libpcap with_libpcre +with_libssh2 with_libdnet with_liblua with_liblinear @@ -1439,6 +1441,7 @@ Optional Packages: --with-libpcre=DIR Use an existing (compiled) pcre lib from DIR/include and DIR/lib. --with-libpcre=included Always use the version included with Nmap + --with-libssh2=DIR Use speficic copy of libssh2 --with-libdnet=DIR Use an existing (compiled) dnet lib from DIR/include and DIR/lib. This is NOT RECOMMENDED because we have made many important fixes to our included libdnet, @@ -5463,8 +5466,8 @@ if test "$with_ndiff" != "no"; then if test -n "$PYTHON"; then # If the user set $PYTHON, use it and don't search something else. - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $PYTHON version >= 2.4" >&5 -$as_echo_n "checking whether $PYTHON version >= 2.4... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $PYTHON version is >= 2.4" >&5 +$as_echo_n "checking whether $PYTHON version is >= 2.4... " >&6; } prog="import sys # split strings by '.' and convert to numeric. Append some zeros # because we need at least 4 digits for the hex conversion. @@ -5482,7 +5485,9 @@ sys.exit(sys.hexversion < minverhex)" { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else - as_fn_error $? "too old" "$LINENO" 5 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "Python interpreter is too old" "$LINENO" 5 fi am_display_PYTHON=$PYTHON else @@ -5494,7 +5499,7 @@ if ${am_cv_pathless_PYTHON+:} false; then : $as_echo_n "(cached) " >&6 else - for am_cv_pathless_PYTHON in python python2 python3 python3.2 python3.1 python3.0 python2.7 python2.6 python2.5 python2.4 python2.3 python2.2 python2.1 python2.0 none; do + for am_cv_pathless_PYTHON in python python2 python3 python3.3 python3.2 python3.1 python3.0 python2.7 python2.6 python2.5 python2.4 python2.3 python2.2 python2.1 python2.0 none; do test "$am_cv_pathless_PYTHON" = none && break prog="import sys # split strings by '.' and convert to numeric. Append some zeros @@ -5610,6 +5615,25 @@ $as_echo "$am_cv_python_platform" >&6; } PYTHON_PLATFORM=$am_cv_python_platform + # Just factor out some code duplication. + am_python_setup_sysconfig="\ +import sys +# Prefer sysconfig over distutils.sysconfig, for better compatibility +# with python 3.x. See automake bug#10227. +try: + import sysconfig +except ImportError: + can_use_sysconfig = 0 +else: + can_use_sysconfig = 1 +# Can't use sysconfig in CPython 2.7, since it's broken in virtualenvs: +# +try: + from platform import python_implementation + if python_implementation() == 'CPython' and sys.version[:3] == '2.7': + can_use_sysconfig = 0 +except ImportError: + pass" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON script directory" >&5 @@ -5623,7 +5647,14 @@ else else am_py_prefix=$prefix fi - am_cv_python_pythondir=`$PYTHON -c "import sys; from distutils import sysconfig; sys.stdout.write(sysconfig.get_python_lib(0,0,prefix='$am_py_prefix'))" 2>/dev/null` + am_cv_python_pythondir=`$PYTHON -c " +$am_python_setup_sysconfig +if can_use_sysconfig: + sitedir = sysconfig.get_path('purelib', vars={'base':'$am_py_prefix'}) +else: + from distutils import sysconfig + sitedir = sysconfig.get_python_lib(0, 0, prefix='$am_py_prefix') +sys.stdout.write(sitedir)"` case $am_cv_python_pythondir in $am_py_prefix*) am__strip_prefix=`echo "$am_py_prefix" | sed 's|.|.|g'` @@ -5660,7 +5691,14 @@ else else am_py_exec_prefix=$exec_prefix fi - am_cv_python_pyexecdir=`$PYTHON -c "import sys; from distutils import sysconfig; sys.stdout.write(sysconfig.get_python_lib(1,0,prefix='$am_py_exec_prefix'))" 2>/dev/null` + am_cv_python_pyexecdir=`$PYTHON -c " +$am_python_setup_sysconfig +if can_use_sysconfig: + sitedir = sysconfig.get_path('platlib', vars={'platbase':'$am_py_prefix'}) +else: + from distutils import sysconfig + sitedir = sysconfig.get_python_lib(1, 0, prefix='$am_py_prefix') +sys.stdout.write(sitedir)"` case $am_cv_python_pyexecdir in $am_py_exec_prefix*) am__strip_prefix=`echo "$am_py_exec_prefix" | sed 's|.|.|g'` @@ -5732,8 +5770,8 @@ if test "$with_zenmap" != "no"; then if test -n "$PYTHON"; then # If the user set $PYTHON, use it and don't search something else. - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $PYTHON version >= 2.4" >&5 -$as_echo_n "checking whether $PYTHON version >= 2.4... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $PYTHON version is >= 2.4" >&5 +$as_echo_n "checking whether $PYTHON version is >= 2.4... " >&6; } prog="import sys # split strings by '.' and convert to numeric. Append some zeros # because we need at least 4 digits for the hex conversion. @@ -5751,7 +5789,9 @@ sys.exit(sys.hexversion < minverhex)" { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else - as_fn_error $? "too old" "$LINENO" 5 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "Python interpreter is too old" "$LINENO" 5 fi am_display_PYTHON=$PYTHON else @@ -5763,7 +5803,7 @@ if ${am_cv_pathless_PYTHON+:} false; then : $as_echo_n "(cached) " >&6 else - for am_cv_pathless_PYTHON in python python2 python3 python3.2 python3.1 python3.0 python2.7 python2.6 python2.5 python2.4 python2.3 python2.2 python2.1 python2.0 none; do + for am_cv_pathless_PYTHON in python python2 python3 python3.3 python3.2 python3.1 python3.0 python2.7 python2.6 python2.5 python2.4 python2.3 python2.2 python2.1 python2.0 none; do test "$am_cv_pathless_PYTHON" = none && break prog="import sys # split strings by '.' and convert to numeric. Append some zeros @@ -5879,6 +5919,25 @@ $as_echo "$am_cv_python_platform" >&6; } PYTHON_PLATFORM=$am_cv_python_platform + # Just factor out some code duplication. + am_python_setup_sysconfig="\ +import sys +# Prefer sysconfig over distutils.sysconfig, for better compatibility +# with python 3.x. See automake bug#10227. +try: + import sysconfig +except ImportError: + can_use_sysconfig = 0 +else: + can_use_sysconfig = 1 +# Can't use sysconfig in CPython 2.7, since it's broken in virtualenvs: +# +try: + from platform import python_implementation + if python_implementation() == 'CPython' and sys.version[:3] == '2.7': + can_use_sysconfig = 0 +except ImportError: + pass" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON script directory" >&5 @@ -5892,7 +5951,14 @@ else else am_py_prefix=$prefix fi - am_cv_python_pythondir=`$PYTHON -c "import sys; from distutils import sysconfig; sys.stdout.write(sysconfig.get_python_lib(0,0,prefix='$am_py_prefix'))" 2>/dev/null` + am_cv_python_pythondir=`$PYTHON -c " +$am_python_setup_sysconfig +if can_use_sysconfig: + sitedir = sysconfig.get_path('purelib', vars={'base':'$am_py_prefix'}) +else: + from distutils import sysconfig + sitedir = sysconfig.get_python_lib(0, 0, prefix='$am_py_prefix') +sys.stdout.write(sitedir)"` case $am_cv_python_pythondir in $am_py_prefix*) am__strip_prefix=`echo "$am_py_prefix" | sed 's|.|.|g'` @@ -5929,7 +5995,14 @@ else else am_py_exec_prefix=$exec_prefix fi - am_cv_python_pyexecdir=`$PYTHON -c "import sys; from distutils import sysconfig; sys.stdout.write(sysconfig.get_python_lib(1,0,prefix='$am_py_exec_prefix'))" 2>/dev/null` + am_cv_python_pyexecdir=`$PYTHON -c " +$am_python_setup_sysconfig +if can_use_sysconfig: + sitedir = sysconfig.get_path('platlib', vars={'platbase':'$am_py_prefix'}) +else: + from distutils import sysconfig + sitedir = sysconfig.get_python_lib(1, 0, prefix='$am_py_prefix') +sys.stdout.write(sitedir)"` case $am_cv_python_pyexecdir in $am_py_exec_prefix*) am__strip_prefix=`echo "$am_py_exec_prefix" | sed 's|.|.|g'` @@ -6667,6 +6740,92 @@ fi + +have_libssh2=no + +# Check whether --with-libssh2 was given. +if test "${with_libssh2+set}" = set; then : + withval=$with_libssh2; case "$with_libssh2" in + yes) + have_libssh2=yes + ;; + included) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: \"Libssh2 is not included with Nmap. Will try to find system version\"" >&5 +$as_echo "$as_me: WARNING: \"Libssh2 is not included with Nmap. Will try to find system version\"" >&2;} + ;; + *) + have_libssh2=yes + CPPFLAGS="-I$with_libssh2/include $CPPFLAGS" + LDFLAGS="-L$with_libssh2/lib $LDFLAGS" + ;; + esac + +fi + + +if test $have_libssh2 != yes; then + for ac_header in libssh2.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "libssh2.h" "ac_cv_header_libssh2_h" "$ac_includes_default" +if test "x$ac_cv_header_libssh2_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBSSH2_H 1 +_ACEOF + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for libssh2_version in -lssh2" >&5 +$as_echo_n "checking for libssh2_version in -lssh2... " >&6; } +if ${ac_cv_lib_ssh2_libssh2_version+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lssh2 -lm $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char libssh2_version (); +int +main () +{ +return libssh2_version (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_ssh2_libssh2_version=yes +else + ac_cv_lib_ssh2_libssh2_version=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssh2_libssh2_version" >&5 +$as_echo "$ac_cv_lib_ssh2_libssh2_version" >&6; } +if test "x$ac_cv_lib_ssh2_libssh2_version" = xyes; then : + have_libssh2=yes; break +fi + + +fi + +done + +fi + +LIBSSH2_LIBS= +if test $have_libssh2 = yes; then + LIBSSH2_LIBS="-lssh2" + $as_echo "#define HAVE_LIBSSH2 1" >>confdefs.h + +fi + + have_dnet=no requested_included_dnet=no LIBDNETDIR=libdnet-stripped diff --git a/configure.ac b/configure.ac index e8394859d..9a32e6db6 100644 --- a/configure.ac +++ b/configure.ac @@ -509,6 +509,38 @@ AC_SUBST(PCRE_BUILD) AC_SUBST(PCRE_CLEAN) AC_SUBST(PCRE_DIST_CLEAN) + +have_libssh2=no +AC_ARG_WITH(libssh2, +AC_HELP_STRING([--with-libssh2=DIR], [Use speficic copy of libssh2]), +[ case "$with_libssh2" in + yes) + have_libssh2=yes + ;; + included) + AC_MSG_WARN("Libssh2 is not included with Nmap. Will try to find system version") + ;; + *) + have_libssh2=yes + CPPFLAGS="-I$with_libssh2/include $CPPFLAGS" + LDFLAGS="-L$with_libssh2/lib $LDFLAGS" + ;; + esac] +) + +if test $have_libssh2 != yes; then + AC_CHECK_HEADERS([libssh2.h], + AC_CHECK_LIB(ssh2, libssh2_version, [have_libssh2=yes; break],, [-lm]) + ) +fi + +LIBSSH2_LIBS= +if test $have_libssh2 = yes; then + LIBSSH2_LIBS="-lssh2" + AC_DEFINE(HAVE_LIBSSH2) +fi +AC_SUBST(LIBSSH2_LIBS) + have_dnet=no requested_included_dnet=no LIBDNETDIR=libdnet-stripped diff --git a/nmap_config.h.in b/nmap_config.h.in index 4968cfdab..958cd74d8 100644 --- a/nmap_config.h.in +++ b/nmap_config.h.in @@ -175,6 +175,8 @@ #undef HAVE_PCRE_H +#undef HAVE_LIBSSH2 + #undef HAVE_PCRE_PCRE_H #undef BSD_NETWORKING diff --git a/nse_libssh2.cc b/nse_libssh2.cc new file mode 100644 index 000000000..ab07c6699 --- /dev/null +++ b/nse_libssh2.cc @@ -0,0 +1,463 @@ +/* Binding for the libssh2 library. Note that there is not a one-to-one correspondance + * between functions in libssh2 and the binding. + * Currently, during the ssh2 handshake, a call to nsock.recieve may result in an EOF + * error. This appears to only occur when stressing the ssh server (ie during a brute + * force attempt) or while behind a restrictive firewall/IDS. + * by Devin Bjelland + */ + +extern "C" { + #include "lua.h" + #include "lauxlib.h" +} + +#include "nse_debug.h" +#include "nse_nsock.h" +#include "nse_utility.h" + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +enum { + SSH2_UDATA = lua_upvalueindex(1) +}; + +struct ssh_userdata { + int sp[2]; + LIBSSH2_SESSION *session; +}; + +static int ssh_error (lua_State *L, LIBSSH2_SESSION *session, const char *msg) +{ + char *errmsg; + libssh2_session_last_error(session, &errmsg, NULL, 0); + return nseU_safeerror(L, "%s: %s", msg, errmsg); +} + +static int finish_send (lua_State *L) +{ + if (lua_toboolean(L, -2)) { + return 0; + } else { + return lua_error(L); /* uses idx 6 */ + } +} + +static int finish_read (lua_State *L) +{ + struct ssh_userdata *sshu = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2"); + + if (lua_toboolean(L, -2)) { + size_t n = 0; + size_t l; + lua_getuservalue(L, 1); + lua_getfield(L, -1, "sp_buff"); + lua_pushvalue(L, 3); + lua_concat(L, 2); + const char *data = lua_tolstring(L, -1, &l); + lua_pushliteral(L, ""); + lua_setfield(L, 4, "sp_buff"); + while (n < l) { + int rc = write(sshu->sp[1], data+n, l-n); + if(rc == -1 && errno != EAGAIN) { + luaL_error(L, "Writing to socket pair: %s", strerror(errno)); + } else if(rc == -1 && errno == EAGAIN) { + lua_pushlstring(L, data+n, l-n); + lua_setfield(L, 4, "sp_buff"); + break; + } else { + n += rc; + } + } + return 0; + } else { + return lua_error(L); /* uses idx 6 */ + } +} + +static int filter (lua_State *L) +{ + int rc; + char data[4096]; + struct ssh_userdata *sshu = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2"); + + lua_getuservalue(L, 1); + lua_getfield(L, -1, "sock"); + lua_replace(L, -2); + + rc = read(sshu->sp[1], data, sizeof(data)); + if (rc > 0) { + //write data to nsock socket + lua_getfield(L, -1, "send"); + lua_insert(L, -2); /* swap */ + lua_pushlstring(L, data, rc); + lua_callk(L, 2, 2, 0, finish_send); + return finish_send(L); + } else if (rc == -1 && errno != EAGAIN) { + luaL_error(L, "%s", strerror(errno)); + } + + lua_getfield(L, -1, "receive"); + lua_insert(L, -2); /* swap */ + lua_callk(L, 1, 2, 0, finish_read); + return finish_read(L); +} + +static int do_session_handshake (lua_State *L) +{ + int rc; + struct ssh_userdata *sshu; + + assert(lua_gettop(L) == 4); + + sshu = (struct ssh_userdata *) nseU_checkudata(L, 3, SSH2_UDATA, "ssh2"); + while((rc = libssh2_session_handshake(sshu->session, sshu->sp[0])) == LIBSSH2_ERROR_EAGAIN) { + luaL_getmetafield(L, 3, "filter"); + lua_pushvalue(L, 3); + lua_callk(L, 1, 0, 0, do_session_handshake); + } + + if (rc) + luaL_error(L, "Unable to complete libssh2 handshake."); + + lua_pushvalue(L, 3); + return 1; +} + +static int finish_session_open (lua_State *L) { + assert(lua_gettop(L) == 6); + if (lua_toboolean(L, -2)) { + lua_pop(L, 2); + return do_session_handshake(L); + } else { + return lua_error(L); + } +} + +/* Creates libssh2 session, connects to hostname:port and tries to perform a + * ssh handshake on socket. Returns ssh_state on success + * + * session_open(hostname, port) + */ +static int l_session_open(lua_State *L) { + int rc; + + luaL_checkint(L, 2); + + lua_settop(L, 2); + + ssh_userdata *state = (ssh_userdata *)lua_newuserdata(L, sizeof(ssh_userdata)); /* index 3 */ + assert(lua_gettop(L) == 3); + state->session = NULL; + state->sp[0] = -1; + state->sp[1] = -1; + lua_pushvalue(L, lua_upvalueindex(1)); /* metatable */ + lua_setmetatable(L, 3); + lua_newtable(L); + lua_setuservalue(L, 3); + lua_getuservalue(L, 3); /* index 4 */ + assert(lua_gettop(L) == 4); + + state->session = libssh2_session_init(); + libssh2_session_set_blocking(state->session, 0); + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, state->sp) == -1) { + return nseU_safeerror(L, "trying to create socketpair"); + } + rc = fcntl(state->sp[1], F_GETFD); + if (rc == -1) + return nseU_safeerror(L, "%s", strerror(errno)); + rc |= O_NONBLOCK; + rc = fcntl(state->sp[1], F_SETFL, rc); + if (rc == -1) + return nseU_safeerror(L, "%s", strerror(errno)); + + lua_getglobal(L, "nmap"); + lua_getfield(L, -1, "new_socket"); + lua_replace(L, -2); + lua_call(L, 0, 1); + lua_setfield(L, 4, "sock"); + + lua_pushliteral(L, ""); + lua_setfield(L, 4, "sp_buff"); + + assert(lua_gettop(L) == 4); + + lua_getfield(L, 4, "sock"); + lua_getfield(L, -1, "connect"); + lua_insert(L, -2); /* swap */ + lua_pushvalue(L, 1); + lua_pushvalue(L, 2); + lua_callk(L, 3, 2, 3, finish_session_open); + return finish_session_open(L); +} + +/* Returns the SHA1 or MD5 hostkey hash of provided session or nil if it is not available + * + */ +static int l_hostkey_hash(lua_State *L) { + static int hash_option[] = {LIBSSH2_HOSTKEY_HASH_MD5, LIBSSH2_HOSTKEY_HASH_SHA1}; + static int hash_length[] = {16, 20}; + static const char *hashes[] = {"md5", "sha1", NULL}; + + luaL_Buffer B; + struct ssh_userdata *state = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2"); + int type = luaL_checkoption(L, 2, "sha1", hashes); + const unsigned char *hash = (const unsigned char *) libssh2_hostkey_hash(state->session, hash_option[type]); + + if (hash == NULL) + return nseU_safeerror(L, "could not get hostkey hash"); + + luaL_buffinit(L, &B); + for (int i = 0; i < hash_length[type]; i++) { + char byte[3]; /* with space for NUL */ + snprintf(byte, sizeof(byte), "%02X", (unsigned int) hash[i]); + if (i) + luaL_addchar(&B, ':'); + luaL_addlstring(&B, byte, 2); + } + luaL_pushresult(&B); + + return 1; +} + +static int l_set_timeout(lua_State *L) { + struct ssh_userdata *state = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2"); + long timeout = luaL_checklong(L, 2); + libssh2_session_set_timeout(state->session, timeout); + + return 0; +} + +/* Return list of supported authenication methods + * + */ +static int l_userauth_list(lua_State *L) { + struct ssh_userdata *state = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2"); + const char *username = luaL_checkstring(L, 2); + char *auth_list; + + while((auth_list = libssh2_userauth_list(state->session, username, lua_rawlen(L, 2))) == NULL && libssh2_session_last_errno(state->session) == LIBSSH2_ERROR_EAGAIN) { + luaL_getmetafield(L, 1, "filter"); + lua_pushvalue(L, 1); + lua_callk(L, 1, 0, 0, l_userauth_list); + } + + if(auth_list) { + const char *auth = strtok(auth_list, ","); + lua_newtable(L); + do { + lua_pushstring(L, auth); + lua_rawseti(L, -2, lua_rawlen(L, -2)+1); + } while((auth = strtok(NULL, ","))); + libssh2_free(state->session, (void *)auth_list); + } else if (libssh2_userauth_authenticated(state->session)) { + lua_pushliteral(L, "none_auth"); + } else { + return ssh_error(L, state->session, "userauth_list"); + } + return 1; +} + +static int l_userauth_publickey(lua_State *L) { + int rc; + const char *username, *private_key_file, *passphrase, *public_key_file; + struct ssh_userdata *state = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2"); + username = luaL_checkstring(L, 2); + private_key_file = luaL_checkstring(L, 3); + if (lua_isstring(L, 4)) { + passphrase = lua_tostring(L, 4); + } + else { + passphrase = NULL; + } + if (lua_isstring(L, 5)) { + public_key_file = lua_tostring(L, 5); + } + else { + public_key_file = NULL; + } + + while ((rc = libssh2_userauth_publickey_fromfile(state->session, username, public_key_file, private_key_file, passphrase)) == LIBSSH2_ERROR_EAGAIN) { + luaL_getmetafield(L, 1, "filter"); + lua_pushvalue(L, 1); + lua_callk(L, 1, 0, 0, l_userauth_publickey); + } + + if(rc == 0) { + lua_pushboolean(L, 1); + } else { + lua_pushboolean(L, 0); + } + return 1; + + +} + +static int l_read_publickey(lua_State *L) { + FILE *fd; + char c; + const char* publickeyfile = luaL_checkstring(L, 1); + luaL_Buffer publickey_data; + fd = fopen(publickeyfile, "r"); + if (!fd) { + luaL_error(L, "Error reading file"); + } + + luaL_buffinit(L, &publickey_data); + while(fread(&c, 1, 1, fd) && '\r' && c != '\n' && c != ' ') continue; + + while(fread(&c, 1, 1, fd) && '\r' && c != '\n' && c != ' ') { + luaL_addchar (&publickey_data, c); + } + fclose(fd); + + lua_getglobal(L, "require"); + lua_pushstring(L, "base64"); + lua_call(L, 1, 1); + lua_getfield(L, -1, "dec"); + + luaL_pushresult(&publickey_data); + lua_call(L, 1, 1); + + return 1; +} + +static int publickey_canauth_cb(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, const unsigned char *data, size_t data_len, void **abstract) { + return 0; +} + +static int l_publickey_canauth(lua_State *L) { + char *errmsg; + int errlen; + int rc; + const char *username; + unsigned const char *publickey_data; + size_t len; + struct ssh_userdata *state = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2"); + username = luaL_checkstring(L, 2); + if (lua_isstring(L, 3)) { + publickey_data = (unsigned const char*) lua_tolstring(L, 3, &len); + } else { + luaL_error(L, "Invalid public key"); + } + + while ((rc = libssh2_userauth_publickey(state->session, username, publickey_data, len, &publickey_canauth_cb, NULL)) == LIBSSH2_ERROR_EAGAIN) { + luaL_getmetafield(L, 1, "filter"); + lua_pushvalue(L, 1); + lua_callk(L, 1, 0, 0, l_publickey_canauth); + } + libssh2_session_last_error(state->session, &errmsg, &errlen, 0); + if(rc == LIBSSH2_ERROR_ALLOC || rc == LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED) { + lua_pushboolean(L, 1); + //Username/PublicKey combination invalid + } else if(rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED) { + lua_pushboolean(L, 0); + } else { + luaL_error(L, "Invalid Publickey"); + } + return 1; +} + +/* Attempts to authenticate session with provided username and password + * returns true on success and false otherwise + * + * userauth_password(state, username, password) + */ +static int l_userauth_password(lua_State *L) { + int rc; + const char *username, *password; + struct ssh_userdata *state = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2"); + username = luaL_checkstring(L, 2); + password = luaL_checkstring(L, 3); + + while((rc = libssh2_userauth_password(state->session, username, password)) == LIBSSH2_ERROR_EAGAIN) { + luaL_getmetafield(L, 1, "filter"); + lua_pushvalue(L, 1); + lua_callk(L, 1, 0, 0, l_userauth_password); + } + if(rc == 0) { + lua_pushboolean(L, 1); + } else { + lua_pushboolean(L, 0); + } + return 1; +} + +static int l_session_close(lua_State *L) { + struct ssh_userdata *state = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2"); + int rc; + while ((rc = libssh2_session_disconnect(state->session, "Normal Shutdown")) == LIBSSH2_ERROR_EAGAIN) { + luaL_getmetafield(L, 1, "filter"); + lua_pushvalue(L, 1); + lua_callk(L, 1, 0, 0, l_session_close); + } + + if (rc < 0) + luaL_error(L, "unable to disconnect session"); + + if (libssh2_session_free(state->session) < 0) { + luaL_error(L, "unable to free session"); + } + return 0; +} + +static const struct luaL_Reg libssh2 [] = { + {"session_open", l_session_open}, + {"hostkey_hash", l_hostkey_hash}, + {"set_timeout", l_set_timeout}, + {"userauth_list", l_userauth_list}, + {"userauth_publickey", l_userauth_publickey}, + {"read_publickey", l_read_publickey}, + {"publickey_canauth", l_publickey_canauth}, + {"userauth_password", l_userauth_password}, + {"session_close", l_session_close}, + {NULL, NULL} +}; + +static int gc (lua_State *L) +{ + struct ssh_userdata *sshu = (struct ssh_userdata *) nseU_checkudata(L, 1, SSH2_UDATA, "ssh2"); + if (sshu) { + lua_pushvalue(L, lua_upvalueindex(1)); + lua_getfield(L, -1, "session_close"); + lua_insert(L, -2); /* swap */ + lua_pcall(L, 1, 0, 0); /* if an error occurs, don't do anything */ + } + close(sshu->sp[0]); + close(sshu->sp[1]); + return 0; +} + +int luaopen_libssh2 (lua_State *L) { + lua_settop(L, 0); /* clear the stack */ + + luaL_newlibtable(L, libssh2); + + lua_newtable(L); /* ssh2 session metatable */ + lua_pushvalue(L, -1); + lua_pushcclosure(L, gc, 1); + lua_setfield(L, -2, "__gc"); + lua_pushvalue(L, -1); + lua_pushcclosure(L, filter, 1); + lua_setfield(L, -2, "filter"); + + luaL_setfuncs(L, libssh2, 1); + + if(libssh2_init(0) != 0) { + luaL_error(L, "unable to open libssh2"); + } + return 1; +} diff --git a/nse_libssh2.h b/nse_libssh2.h new file mode 100644 index 000000000..578696f5b --- /dev/null +++ b/nse_libssh2.h @@ -0,0 +1,8 @@ +#ifndef LIBSSH2 + +#define LIBSSH2 +#define LIBSSH2LIBNAME "libssh2" + +LUALIB_API int luaopen_libssh2 (lua_State *L); + +#endif diff --git a/nse_main.cc b/nse_main.cc index cfc0f809b..29b55cc27 100644 --- a/nse_main.cc +++ b/nse_main.cc @@ -20,6 +20,7 @@ #include "nse_openssl.h" #include "nse_debug.h" #include "nse_lpeg.h" +#include "nse_libssh2.h" #define NSE_MAIN "NSE_MAIN" /* the main function */ @@ -545,6 +546,9 @@ static void set_nmap_libraries (lua_State *L) {BITLIBNAME, luaopen_bit}, {LFSLIBNAME, luaopen_lfs}, {LPEGLIBNAME, luaopen_lpeg}, +#ifdef HAVE_LIBSSH2 + {LIBSSH2LIBNAME, luaopen_libssh2}, +#endif #ifdef HAVE_OPENSSL {OPENSSLLIBNAME, luaopen_openssl}, #endif diff --git a/nselib/libssh2.luadoc b/nselib/libssh2.luadoc new file mode 100644 index 000000000..2800761a1 --- /dev/null +++ b/nselib/libssh2.luadoc @@ -0,0 +1,71 @@ +-- +-- Provides a binding for the libssh2 library. +-- +-- SSH2 is a complex protocol and libssh2 simplifies many tasks involved in +-- interacting with ssh servers. This module provides bindings for some of the +-- most commonly used libssh2 functions. +-- +-- For perfomance reasons, the modules reuses the NSE's existing nsock socket +-- pool. In order to accomplish this, libssh2 is a given a unix socket pair +-- instead of a network socket. As a result, this library is *nix only at the +-- current time. +-- +-- @author Devin Bjelland +-- @copyright same as Nmap + +module "libssh2" + +--- Creates libssh2 session and performs handshake +-- @param host Host to connect to. +-- @param port Port to connect to. +-- @return session or nil on failure +function session_open(host, port) + +--- Returns SHA1 or MD5 hostkey hash of session +-- @param session Connected libssh2 session. +-- @param hashtype (optional) "sha1" or "md5" (sha1 is default) +-- @return hostkey hash of server +function hostkey_hash(session, hashtype) + +--- Sets timeout of libssh2 session +-- @param session Connected libssh2 session. +-- @param timeout Timeout for session in milliseconds. +function set_timeout(session, timeout) + +--- Returns list of authentication methods supported by the server +-- @param session Connected libssh2 session. +-- @return List of supported authentication methods/ +function userauth_list(session) + +--- Attempts to authenicate libssh2 session using provided credentials +-- @param username Username to authenicate as. +-- @param password Password to authenicate with. +-- @return true/false, depending on success +function userauth_password(session, username, password) + +--- Attempts to authenticate libssh2 session using provided publickey +-- @param session Connected libssh2 session +-- @param username Username to authenicate as +-- @param privatekeyfile File containing privatekey +-- @param passphrase Passphrase for privatekey +-- @param publickeyfile File containing publickey. Not necessary if libssh2 is +-- compiled against OpenSSL +-- @return true/false, depending on success +function userauth_publickey(session, username, privatekeyfile, passphrase publickeyfile) + +--- Read publickey from id_*.pub type key file +-- @param publickeyfile File containing publickey +-- @return string containing raw key data +function read_publickey(publickeyfile) + +--- Checks to see if ssh server accepts public key for authentication as given user. +-- This doesn't require the private key as it doesn't finish authenticating. +-- @param session Connected libssh2 session +-- @param username Username to authenicate as +-- @param publickeydata String containing raw publickey blob +-- @return true/false, depending on whether user can authenticate with given key +function publickey_canauth(session, username, publickeydata) + +--- Gracefully closes connected libssh2 session +-- @param session Connected libssh2 session +function session_close(session) diff --git a/nselib/ssh1.lua b/nselib/ssh1.lua index 0d4d67268..2cdb49d85 100644 --- a/nselib/ssh1.lua +++ b/nselib/ssh1.lua @@ -112,6 +112,12 @@ fetch_host_key = function(host, port) end end +--- Returns key fingerprint in hexadecimal +fingerprint = function( hostkey ) + local fingerprint = openssl.md5(hostkey) + return stdnse.tohex(fingerprint,{separator=":",group=2}) +end + --- Format a key fingerprint in hexadecimal. -- @param fingerprint Key fingerprint. -- @param algorithm Key algorithm. diff --git a/scripts/ssh-auth-methods.nse b/scripts/ssh-auth-methods.nse new file mode 100644 index 000000000..23e4bf631 --- /dev/null +++ b/scripts/ssh-auth-methods.nse @@ -0,0 +1,35 @@ +local shortport = require "shortport" +local stdnse = require "stdnse" +local libssh2 = require "libssh2" + +description = [[ +Returns authenication methods a ssh server supports. +]] + +--- +-- @usage +-- nmap -p 22 --script ssh-auth-methods --script-args="ssh.user=" +-- +-- @output +-- 22/tcp open ssh syn-ack +-- | ssh-auth-methods: +-- | Supported authentication methods: +-- | publickey +-- |_ password + +author = "Devin Bjelland" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" + +local username = stdnse.get_script_args("ssh.user") or stdnse.generate_random_string(5) +portrule = shortport.port_or_service(22, 'ssh') + +action = function (host, port) + local result = stdnse.output_table() + + local session = libssh2.session_open(host, port.number) + local authmethods = libssh2.userauth_list(session, username) + + result["Supported authentication methods"] = authmethods + + return result +end diff --git a/scripts/ssh-brute.nse b/scripts/ssh-brute.nse new file mode 100644 index 000000000..e1654d8f9 --- /dev/null +++ b/scripts/ssh-brute.nse @@ -0,0 +1,118 @@ +local shortport = require "shortport" +local stdnse = require "stdnse" +local brute = require "brute" + +local libssh2 = stdnse.silent_require "libssh2" + +description = [[ +Performs brute-force password guessing against ssh servers. +]] + +--- +-- @usage +-- nmap -p 22 --script ssh-brute --script-args userdb=users.lst,passdb=pass.lst \ +-- --script-args ssh-brute.timeout=4s +-- +-- @output +-- 22/ssh open ssh +-- | ssh-brute: +-- | Accounts +-- | username:password +-- | Statistics +-- |_ Performed 32 guesses in 25 seconds. +-- +-- @args ssh-brute.timeout Connection timeout (default: "5s") + +author = "Devin Bjelland" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {'brute', 'intrusive'} + +portrule = shortport.port_or_service(22, 'ssh') + +local arg_timeout = stdnse.get_script_args(SCRIPT_NAME .. ".timeout") or "5s" + +Driver = { + new = function(self, host, port, options) + stdnse.debug(2, "creating brute driver") + local o = {} + setmetatable(o, self) + self.__index = self + o.host = host + o.port = port + o.options = options + return o + end, + + connect = function (self) + stdnse.debug(2, "connecting to %s:%d", self.host.ip, self.port.number) + local status, session, err = pcall(libssh2.session_open, self.host, self.port.number) + if not status then + stdnse.debug(2, "libssh2 error: %s", session) + local err = brute.Error:new(session) + err:setAbort(true) + return false, err + elseif not session then + stdnse.debug(2, "failure to connect: %s", err); + local err = brute.Error:new(err) + err:setAbort(true) + return false, err + else + self.ssh_session = session + libssh2.set_timeout(self.ssh_session, self.options.ssh_timeout) + return true + end + end, + + disconnect = function(self) + stdnse.debug(2, "disconnecting from %s:%d", self.host.ip, self.port.number); + local status, err = pcall(libssh2.session_close, self.ssh_session) + if not status then + stdnse.debug(2, "libssh2 error: %s", ok) + end + end, + + login = function(self, username, password) + stdnse.verbose(2, "Trying username/password pair: %s:%s", username, password) + local status, ok = pcall(libssh2.userauth_password, self.ssh_session, username, password) + if not status then + stdnse.debug(2, "libssh2 error: %s", ok) + return false, brute.Error:new(ok) + elseif not ok then + stdnse.debug(2, "login failed for %s:%s", username, password) + return false, brute.Error:new("login failed") + else + stdnse.verbose(1, "Found working Credentials: %s:%s", username, password) + return true, brute.Account:new(username, password, "OPEN") + end + end, +} + +password_auth_allowed = function (host, port) + local status, ssh_session = pcall(libssh2.session_open, host, port.number) + if status and ssh_session then + local status, methods = pcall(libssh2.userauth_list, ssh_session, "root") + if status then + for _, value in pairs(methods) do + if value == "password" then + return true + end + end + end + end + return false +end + +action = function (host, port) + local timems = stdnse.parse_timespec(arg_timeout) --todo: use this! + local ssh_timeout = 1000 * timems + if password_auth_allowed(host, port) then + local options = {ssh_timeout = ssh_timeout} + local engine = brute.Engine:new(Driver, host, port, options) + engine.options.script_name = SCRIPT_NAME + local _, result = engine:start() + return result + else + return "Password authenication not allowed" + end +end + diff --git a/scripts/ssh-vuln-hostkey.nse b/scripts/ssh-vuln-hostkey.nse new file mode 100644 index 000000000..62d1c453a --- /dev/null +++ b/scripts/ssh-vuln-hostkey.nse @@ -0,0 +1,128 @@ +local shortport = require "shortport" +local ssh2 = require "ssh2" +local ssh1 = require "ssh1" +local stdnse = require "stdnse" + +local io = require "io" +local string = require "string" +local table = require "table" + +description = [[ +Checks if ssh server has a predictable hostkey by checking it against a list of fingerprints +generated by HD Moore. You have to download these hostkeys separately and specify their directory +as the fingerprintdir variable. The keys are available at http://itsecurity.net/debian_ssh_scan_v4.tar.bz2. Additionally, you can specify a file ssh hostkey fingerprints, one per line, and the scripts will report if the hostkey matches one of the provided fingerprints. +]] + +--- +-- @usage +-- nmap -p 22 --script ssh-vuln-hostkey \ +-- --script-args fingerprintdir= +-- +-- @output +-- +-- 22/tcp open ssh syn-ack +-- | ssh-vuln-hostkey: +-- | Weak hostkeys: +-- |_ 2048 6d:cd:2a:8b:dc:3e:e0:92:00:47:59:16:8c:8b:17:70 (RSA) +-- +-- +-- @usage +-- -- nmap -p 22 --script ssh-vuln-hostkey \ +-- --script-args fingerprintfile= +-- +-- @output +-- +-- 22/tcp open ssh syn-ack +-- | ssh-vuln-hostkey: +-- | Listed hostkeys: +-- |_ 2048 6d:cd:2a:8b:dc:3e:e0:92:00:47:59:16:8c:8b:17:70 (RSA) +-- +-- @args fingerprintdir Directory containing vulnerable fingerprints +-- @args fingerprintfile File containing fingerprints to check against +-- + +author = "Devin Bjelland" + +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"safe","default","discovery"} + +dependencies = {"ssh-hostkey"} + +portrule = shortport.port_or_service(22, 'ssh') + +local fingerprintdir = stdnse.get_script_args("fingerprintdir") +local fingerprintfile = stdnse.get_script_args("fingerprintfile") + +action = function (host, port) + local key + local keys = {} + local r = stdnse.output_table() + local w = {} + local listed_hostkeys = {} + local found_listed_key + local found_weak_key + + if nmap.registry.sshhostkey and nmap.registry.sshhostkey[host.ip] then + keys = nmap.registgry.sshhostkey[host.ip] + else + key = ssh2.fetch_host_key( host, port, "ssh-rsa" ) + if key then table.insert( keys, key ) end + + key = ssh2.fetch_host_key( host, port, "ssh-dss" ) + if key then table.insert( keys, key ) end + end + + for _,key in ipairs(keys) do + local fingerprint = stdnse.tohex(key.fingerprint) + stdnse.debug("fingerprint: " .. fingerprint) + if fingerprintdir then + if key.key_type == "ssh-rsa" then + for line in io.lines(fingerprintdir .. "ssh_rsa_" .. key.bits .. "_keys.txt") do + local stripped_line = string.gsub(line, "-.*", "") + if stripped_line == fingerprint then + found_weak_key = true + stdnse.verbose("Found weak hostkey: " .. ssh1.fingerprint_hex(key.fingerprint, key.algorithm, key.bits)) + table.insert(w, ssh1.fingerprint_hex(key.fingerprint, key.algorithm, key.bits)) + end + end + elseif key.key_type == "ssh-dss" and key.bits == "1024" then + for line in io.lines(fingerprintdir .. "ssh_dsa_1024_keys.txt", "r") do + local stripped_line = string.gsub(line, "-.*", "") + if stripped_line == fingerprint then + found_weak_key = true + stdnse.verbose("Found weak hostkey: " .. ssh1.fingerprint_hex(key.fingerprint, key.algorithm, key.bits)) + table.insert(w, ssh1.fingerprint_hex(key.fingerprint, key.algorithm, key.bits)) + end + end + end + end + if fingerprintfile then + for line in io.lines(fingerprintfile) do + local stripped_line = string.gsub(line, ":", "") + if stripped_line == fingerprint then + found_listed_key = true + table.insert(listed_hostkeys, ssh1.fingerprint_hex(key.fingerprint, key.algorithm, key.bits)) + stdnse.verbose("Found hostkey on list:" .. ssh1.fingerprint_hex(key.fingerprint, key.algorithm, key.bits)) + end + end + end + end + + if not found_weak_key then + w = "No weak hostkeys found" + end + + if not found_listed_key then + list_hostkey = "No listed hostkeys found" + end + + if fingerprintdir then + r["Weak hostkeys"] = w + end + + if fingerprintfile then + r["Listed hostkeys"] = listed_hostkeys + end + + return r +end