diff --git a/scan-no-yaml.py b/scan-no-yaml.py new file mode 100644 index 0000000..b997261 --- /dev/null +++ b/scan-no-yaml.py @@ -0,0 +1,714 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Original code by Planet-Work +# Forked by Malin Cenusa for Lunarpages (malin.cenusa@lunarpages.com) +# +# - sub-version forked to ditch yaml and beautify output +# To Do: +# - Organize +# - Add more patterns - work in progress +# - remove false positives - work in progress + +import os +import re +import fnmatch + +whitelist = [ + '/lp-msh-scanner/', + '/LP-MSH-Scanner-master/' + '/LP-MSH-Scanner-master/scan.php' + '/LP-MSH-Scanner-master/mscan.php' + '/._', + 'cache/object/000000/', + 'libraries/simplepie/simplepie.php', + '/smarty/cache/', + 'SimplePie/Misc.php', + 'libraries/phpxmlrpc/xmlrpc.php', + '/typography/googlefonts.php', + '/var/cache/deliverycache_', + 'GPAO/ajouter_clients.php', + '/GPAO/modifier_clients2.php', + 'libraries/openid/Auth/OpenID/Consumer.php', + '/includes/utf/data/', + 'libraries/openid/Auth/OpenID/Association.php', + '/module_courriel/form_courriel.php', + '/pro-settings.php', + '/themes/MmoPress/header.php', + '/themes/MmoPress/functions.php', + 'tcpdf.php', + '/wp-includes/upgrade.php', + '/includes/et_lb_sample_layouts.php', + '/sp_compatibility_test.php', + '/tmp/cache/skel/html____', + '/assets/styles/css-', + '/help.inc.php', + 'achat/fin_commande.php', + '/accessibility.inc.php', + '/optimizePressPlugin/lib/', + '/wfBrowscapCache.php', + 'kick-it-2x/footer.php', + '/blueidea-10/footer.php', + '/three_tennis_balls_scoreboard_spj005/footer.php', + '/page-google-maps/view/widget_js.php', + '/gardens-amidst-jungle/footer.php', + '/With_Rainbows/footer.php', + '/naturetravel/footer.php', + '/pages/1250793045.php', + '/tinymce/preview.php', + 'polldata/session.php', + '/tmplvars.inc.php', + '/Command/Factory/FactoryInterface.php', + '/Service/Exception/DescriptionBuilderException.php', + 'iconic-navigation/iconavs_icons.php', + '/classes/TinyPspellShell.class.php', + 'aklazy/aklazy/main.php', + '/inc/RecupDocsTheme.php', + '/function.eval.php', + '/trans_box.php', + 'newstoday/footer.php', + '/md_mix.inc.php', + '/import.inc.php', + '/cache/twig/', + '/transliteration/data/', + '/Transliterator/data/x', + '/patterns/', + '/ecrire/lang/public_', + 'phocagallery/render/renderinfo.php', + 'Faker/Provider', + '/ecrire/lang/ecrire_', + '/ecrire/lang/spip_', + '/symfony/vendors.php', + 'stat/images/os.php', + 'js_composer/config/templates.php', + 'leaflet-maps-marker/leaflet-exportcsv.php', + '/auth/iso639-2.php', + '/includes/lang/.*\.inc\.php', + '.yml.php', + '.js.php', + 'cache/siteCache.idx.php', + '/wpsr-services-selector.php', + '/mpdf56/examples', + '/mpdf50/examples', + 'wp-admin-bar-removal/wp-admin-bar-removal.php', + 'includes/admin/dummy.php', + 'tcpdf/fonts/', + '/shortcodes/googlemaps.php', + '/infocus/activation.php', + 'admin/core/core-help-text.php', + '/theme-check/checks/badthings.php', + '_compatibility_test/sdk_compatibility_test.php', + 'compatibility_test/sp_compatibility_test.php', + '/panel/shortcodes/ui.php', + 'tcpdf/fonts/pdfasymbol.php', + 'libraries/facebook-php-sdk/src/base_facebook.php', + 'Auth/NTLMAuthenticatorTest.php', + 'vendor/vendors.php', + '/bin/vendors.php', + 'ap_ProdProjectContainer.php', + 'lib/htmLawed.php', + '/editors/xinha.php', + '/akismet/views/notice.php', + 'System/Model/Base/RouteGateway.php', + 'System/Model/Base/AdminrouteGateway.php ', + 'Comment/Model/Base/PostGateway.php', + '/Sluggable/Util/data/x', + '/ttfontdata/', + 'GDEFdata.php', + 'includes/facebook-php-sdk/base_facebook.php', + 'wp-content/plugins/wysija-newsletters/helpers/render_engine.php', + 'wp-content/plugins/wysija-newsletters/views/back/campaigns.php', + 'wp-content/plugins/wysija-newsletters/helpers/render_engine.php', + 'wp-content/plugins/broken-link-checker/idn/uctc.php', + 'wp-includes/class-IXR.php', + 'wp-includes/SimplePie/Sanitize.php', + 'wp-admin/includes/ajax-actions.php', + 'wp-content/plugins/codestyling-localization/codestyling-localization.php', + '/nusoap.php', + 'shortcodes/vc_raw_html.php', + '/class-pclzip.php', + '/pclzip/pclzip.lib.php', + '/pclzip/pclzip.php', + '/inc_php/framework/base_admin.class.php', + 'wp-includes/class-wp-atom-server.php', + 'wp-includes/class-simplepie.php', + 'wp-includes/class-wp-customize-widgets.php', + 'wp-admin/includes/file.php', + 'wp-admin/js/revisions-js.php', + '/wp-app.php', + '/CallbackColumn.php', + 'sitepress-multilingual-cms/res/languages.csv.php', + 'content/plugins/better-wp-security/core/class-itsec-core.php', + 'content/plugins/w3-total-cache/lib/W3/Plugin/Minify.php', + 'content/plugins/w3-total-cache/lib/SNS/sdk.class.php', + 'content/plugins/codestyling-localization/codestyling-localization.php', + '_tcpdf/tcpdf.php', + 'tcpdf/examples/example_', + 'src/ext/htmlsql.class.php', + 'plugins/wplite/wplite.php', + 'plugins/nospamnx/ws1.php', + 'bepro-listings/bepro_listings.php', + '/wpposticon.php', + '/better-wp-security/core/class-itsec-core.php', + '/app/cache/dev/', + '/app/cache/prod/', + 'administrator/components/com_remository/admin.remository.html.php', + 'administrator/components/com_remository/admin.remository.html.php', + 'ultimate-coming-soon-page/framework/framework.php', + 'wp-content/plugins/shortcodes-ultimate/inc/core/shortcodes.php', + 'wp-content/plugins/shortcodes-ultimate/inc/vendor/sunrise.php', + 'wp-content/plugins/ultimate-coming-soon-page/framework/framework.php', + '/XmlRpcClientRemote/XmlRpc.php', + 'sitepress-multilingual-cms/inc/installer/includes/installer.class.php', + '/Amf/Server.php', + 'src/facebook.php', + '/spellchecker.php', + 'util/php/ajax/filters.php', + 'lib/class/SEO_URL.class.php', + 'ebservice/dispatcher.php', + 'include/js/jsval.php', + 'include/js/lytebox.php', + 'include/class.TCPDF.php', + 'SimplePie/Sanitize.php', + 'plugins/gravityforms/common.php', + 'plugins/gravityforms/form_detail.php', + 'gravityforms/includes/addon/class-gf-results.php', + 'w3-total-cache/inc/functions/multisite.php', + '/ezpublish/cache/', + 'administrator/components/com_securitycheckpro/scans/', + 'custom-fields/typography/googlefonts-array.php', + 'wp-content/uploads/sucuri/sucuri-sitecheck.php', + 'wp-content/plugins/akeebabackupcore/app/restore.php', + '/includes/utf/data/recode_cjk.php', + '/kernel/includes/smarty/plugins/modifier.base64decode.php', + '/kernel/includes/smarty/plugins/function.mime_decode.php', + '/common/html/scripts/preview.php', + '/html/scripts/core_functions/crypto/crypto_functions.php', + '/html/scripts/getwidget.php', + '/html/scripts/cc_after.php', + +] + +debug = True +line_early = 15 +scoring = { + 'WHITELISTED': (-10, u'Manually whitelisted file'), + 'WHITELISTED_LINE': (-10, u'Manually whitelisted line'), + 'PHP_COMMENTS': (-10, u'File starts with a proper description'), + 'CLASS_FUNCTION': (-10, u'Class function defined early'), + 'BASE64_STRING': (50, u'base64 string found'), + 'CRYPT_PHP': (50, u'CryptoPHP inclusion for social.png'), + 'PHP_SHELL': (50, u'Shell Script'), + 'PHP_OBFUSC_SHELL': (9, u'Obfuscated Shell Script'), + 'ACCESS_DENIED': (-30, u'Early block execution'), + 'JAVASCRIPT_HACK': (50, u'Javascript'), + 'HAS_EVAL': (2, u'Has eval()'), + 'HAS_EVAL_EARLY': (9, u'Has eval() early'), + 'HAS_CALL_FUNC_EARLY': (3, u'Has call_user_func() early'), + 'HAS_BASE64DECODE': (2, u'Has base64_decode() or str_rot13()'), + 'HAS_BASE64DECODE_EARLY': (9, u'Has base64_decode() or str_rot13() early'), + 'HAS_MAIL': (1, u'Has mail()'), + 'HAS_MAIL_EARLY': (2, u'Has mail() early'), + 'LONG_LINE': (5, u'Has a line of more than 1000 characters'), + 'LONG_LINE_EARLY': (8, u'Has a line of more than 1000 characters early'), + 'VERY_LONG_LINE': (5, u'Has a line of more than 3000 characters'), + 'VERY_LONG_LINE_EARLY': (9, u'Has a line of more than 3000 characters early'), + 'MD5_VAR': (9, u'Has a MD5 encoded variable'), + 'INCLUDE_REQUIRE': (-2, u'Has include() or require() without http'), + 'COOKIE_FORM1': (20, u'Has form1=@$_COOKIE'), + 'MAIL_X_HEADER': (5, u'Has mail.add_x_header'), + 'SET_TIME_0': (5, u'Has set_time_limit(0)'), + 'SET_ERRORREPORTING_0': (2, u'Has error_reporting(0)'), + 'SET_TIMELIMIT_0': (2, u'Has memory_limit(0)'), + 'SET_IGNOREUSERABORT_0': (2, u'Has ignore_user_abort()'), + 'UPLOAD_FILE': (2, u'Has move_uploaded_file()'), + 'FEW_LINES': (0, u'Has few lines'), + 'EMPTY_FILE': (-100, u'Empty file'), + 'MANY_LINES': (-2, u'Has too many lines'), + 'MANY_LINES2': (-5, u'Big file with lines'), + 'MANY_LINES3': (-10, u'Too big file with lines'), + 'BAD_NEWLINES': (-5, u'One line file without newline'), + 'NO_PHP_START': (-5, u'PHP start without opening tag: 50: + score.append(('CONCAT_STRING', '%i concat' % + (l.count('","') + l.count("'.'")))) + if l.count('$GLOBALS[') > 20: + score.append( + ('MANY_GLOBALS', '%i globals' % (l.count('","') + l.count("'.'")))) + l = l.replace('","', '').replace("'.'", '') + if ('die("Access Denied");' in l or '' in l or '' in l + or l.find('' in l or '= 0: + score.append(('BIN_HOST', '')) + if re.compile('<\?php\s*\$([a-z]){1,10}\s*=\s*\'.*\$([a-z]){1,10}=explode\(chr\(\(([0-9]){1,4}[-+]([0-9]){1,4}\)\).*\$([a-z]){1,10}=\(([0-9]){1,4}[-+]([0-9]){1,10}\).*-1;\s*\?>').match(l): + score.append(('EITEST', '')) + if ('if( !isset($gCms) ) exit;' in l or + "if( !defined( '_VALID_MOS' )" in l or + "if (!defined('IN_PHPBB')" in l or + "defined('_JEXEC') " in l or "or more information: see languages.txt in the la" in l) \ + and line_num == 2: + score.append(('ACCESS_DENIED', '')) + # if 'Restricted access' in l: + # print line_num + if line_num in [2, 3] and ('Direct Access to this location is not allo' in l or 'Restricted access' in l or 'defined(\'_JEXEC\') or die' in l): + score.append(('ACCESS_DENIED', '')) + if line_num == 1 and not l.strip().find('' . $contents);" in l and not "_eval(" in l: + if line_num < line_early: + score.append(('HAS_EVAL_EARLY', 'line %i' % line_num)) + else: + score.append(('HAS_EVAL', 'line %i' % line_num)) + if l.find('mail(') == 0 or ' mail(' in l: + if line_num < line_early: + score.append(('HAS_MAIL_EARLY', 'line %i' % line_num)) + else: + score.append(('HAS_MAIL', 'line %i' % line_num)) + if line_num < line_early and 'call_user_func' in l: + score.append(('HAS_CALL_FUNC_EARLY', 'line %i' % line_num)) + + if 'agent' in l.lower() and 'google' in l.lower(): + score.append(('UA_GOOGLE', '')) + + if 'base64_decode(' in l or 'base64_decode (' in l \ + or 'str_rot13(' in l or 'str_rot13 (' in l: + if 'CmVycm9yX3JlcG9ydGluZygwKTsKJHFhe' in l or 'FZY1EuQIEkXvMlZ3yBBTrCVmLkH' in l or '" in l or 'b+=String.fromCharCode(a.charCodeAt(i)^2' in l or "eval(eval('String.fromCharCode(" in l: + score.append(('JAVASCRIPT_HACK', '')) + elif "'FilesMan';" in l or '"FilesMan";' in l or 'Web Shell by Guest' in l or 'File uppato senza problemi' in l or 'echo"gagal"' in l \ + or "wpplugin_action = 'WPcheckInstall'" in l or "'bas'.'e6'.'4_d'.'ecode'" in l \ + or 'shell_exec("sh inst")' in l or 'index.php replaced successufuly!' in l \ + or 'NShell t35' in l \ + or 'CorporateSignonTelecode' in l \ + or 'UDP Shell!' in l \ + or 'eval("?>".gzuncompress(base64_decode(' in l \ + or 'Mr.HarchaLi' in l \ + or ('eval(gzinflate(base64_decode(' in l and line_num < 5)\ + or 'dropsforums.ru' in l \ + or 'DamaneDz' in l \ + or "$s='str_r'.'o'.'t13';" in l \ + or '){type1_send();exit();}elseif(isset' in l \ + or '\\x65\\x76\\x61\\x6c\\x20\\x28\\x20\\x67\\x7a\\x69\\x6e\\x66\\x6c\\x61\\x74' in l \ + or 'CH (UBS Spam) ' in l \ + or "$words['cantbeshown']" in l \ + or '"netstat -an' in l \ + or '($action==""||$password==""||$filename==""||$body=="")' in l \ + or "strrev('edoced_46esab')" in l \ + or (l.find("return base64_decode($") == 0 and 'for($i=0; $i < strlen($' in previous_line) \ + or 'function multiRequest($data, $options = array(), $oneoptions = array())' in l \ + or (l.find('GIF89') == 0 and line_num == 1) \ + or (line_num == 1 and "@$_COOKIE[" in l and "();}?>" in l) \ + or (line_num == 1 and '@move_uploaded_file' in l) \ + or ("move_uploaded_file/*;*/" in l) \ + or 'Database Emails Extractor' in l \ + or ("

!PhpSend!

" in l) \ + or 'Done ==> $userfile_name' in l \ + or ('$files=fopen(\'../../../\'.$filepaths.' in l and ',"w+");' in l) \ + or "chmod ($_REQUEST['p1'], $_REQUEST['p2']);" in l \ + or "\\x62\\x61\\x73\\x65\\x36\\x34\\x5F\\x64\\x65\\x63\\x6F\\x64\\x65" in l \ + or "\\x73\\x74\\x72\\x5f\\x72\\x6f\\x74\\x31\\x33" in l \ + or "\\x67\\x7a\\x75\\x6e\\x63\\x6f\\x6d\\x70\\x72\\x65\\x73\\x73" in l \ + or (line_num == 2 and "$ref = $_SERVER['HTTP_USER_AGENT'];" in l) \ + or (line_num < 4 and "passthru($_POST[" in l) \ + or (line_num == 1 and '$stg="ba"."se"."64_d"."ecode";eval($stg(' in l) \ + or '(edoced_46esab(etalfnizg(lave' in l \ + or "file_put_contents('1.txt', print_r" in l \ + or 'function wp_cd(' in l: + score.append(('PHP_SHELL', '')) + + if 'move_uploaded_file(' in l: + score.append(('UPLOAD_FILE', '')) + + if ('" in l ) \ + or (line_num == 1 and 'eval(' in l and '$_REQUEST[' in l and ' = fopen' in l and '; exit(); } ?>' in l) \ + or (line_num == 1 and 'if(!isset($GLOBALS[' in l) \ + or (line_num == 2 and 'if(!empty($_POST[' in l and '){eval($_POST' in l) \ + or (line_num == 2 and 'if(isset($_POST[' in l and 'eval($_POST[' in l) \ + or '%x5c%x7825-bubE' in l: + cleanup_available = True + score.append(('PHP_SHELL', '')) + + if ('"' in l or "'" in l) and not '$UTF8_TO_ASCII' in previous_line: + if len(l) > 3000 and not has_very_long_line: + has_long_line = has_very_long_line = True + if line_num < line_early: + score.append( + ('VERY_LONG_LINE_EARLY', 'line %i' % line_num)) + else: + score.append(('VERY_LONG_LINE', 'line %i' % line_num)) + elif len(l) > 1000 and not has_very_long_line and not has_long_line: + has_long_line = True + if line_num < line_early: + score.append(('LONG_LINE_EARLY', 'line %i' % line_num)) + else: + score.append(('LONG_LINE', 'line %i' % line_num)) + + if "$cidinfo['uni2cid'] = array(" in l or 'php return unserialize(' in l \ + or (' 12 and line_num < 30 and first_lines[0] == ' 40 and first_lines[3][0] == ' ': + score.append(('PHP_OBFUSC_SHELL', '')) + + if line_num == 0 or (line_num == 1 and ( len(first_lines[0]) < 10 or 'Silence is golden.' in first_lines[0])) \ + or (line_num == 2 and len(first_lines[0]) < 10 and 'Silence is golden.' in first_lines[1]): + score.append(('EMPTY_FILE', '')) + if line_num == 1 and (' 7 and (first_lines[0] == ' 3 and ('@author : ' in first_lines[1] or '@author : ' in first_lines[0] or '@version' in first_lines[2] or 'Legacy Mode compatibility' in first_lines[2]): + score.append(['PHP_COMMENTS', w]) + + total_score = 0 + score_details = [] + score_done = [] + for sco, detail in score: + if sco in score_done: + continue + score_done.append(sco) + total_score += scoring[sco][0] + score_details.append({'rule': sco, + 'details': detail.encode('utf-8'), + 'score': scoring[sco][0], + 'description': scoring[sco][1].encode('utf-8')}) + + if filename[0] != '/': + filename = os.getcwd() + '/' + filename + + if clean_PCT4 or clean_evalbase64: + cleanup_available = True + if cleanup_available and line_num == 1: + cleanup_available = False + + return {'filename': filename, + 'score': total_score, + 'mtime': os.stat(filename).st_mtime, + 'ctime': os.stat(filename).st_ctime, + 'details': score_details, + 'cleanup': cleanup_available} + #print total_score, filename, '::'.join(score_details).encode('utf-8') + + # from subprocess import Popen + # if clean_PCT4: + # print "PCT4", filename, "CLEANED" + # Popen(['perl', '-pi', '-e','s/<\?php.*$sF=.PCT4B.*}\?>//g',filename]) + # elif clean_evalbase64: + # print "EVAL+BASE64", filename, "CLEANED" + # Popen(['perl', '-pi', + # '-e','s/\?php\s*eval\(base64_decode\("[a-zA-Z0-9\/=]*"\)\);/?php/g',filename]) + + +if __name__ == '__main__': + SERIALIZER = 'json' + import argparse +# try: +# import yaml +# SERIALIZER = 'yaml' +# except ImportError: + import json + parser = argparse.ArgumentParser( + description='Check directory or file for PHP malwares.') + parser.add_argument('directory_file', + help='directory or file to check', type=str) + parser.add_argument('--post', dest='post', type=str, default=None, + help='POST the result to an URL "%%TOKEN%%" will be \ + replaced by a uuid random token and "%%HOSTNAME%%" by \ + hostname (default: no post)') + parser.add_argument('--minscore', dest='minscore', type=int, + default=-5, help='Minimum score (default: -5)') + parser.add_argument('--maxresults', dest='maxresults', type=int, + default=500, + help='Maximum number or results (default: 500)') + args = parser.parse_args() + basedir = args.directory_file + RESULT_POST = args.post + MIN_SCORE = args.minscore + MAX_RESULTS = args.maxresults + # print(args) + # import socket + # RESULT_POST = 'https://xxx.com/%s/%s' % (socket.gethostname(),token) + + if RESULT_POST: + import socket + import uuid + RESULT_POST = RESULT_POST.replace('%HOSTNAME%', socket.gethostname()) + RESULT_POST = RESULT_POST.replace('%TOKEN%', '%s' % uuid.uuid1()) + + results = [] + if os.path.isdir(basedir): + for root, dirnames, filenames in os.walk(basedir): + for filename in filenames: + if fnmatch.fnmatch(filename, '*.php') or \ + fnmatch.fnmatch(filename, '*.txt') or \ + fnmatch.fnmatch(filename, '*.js'): + hacked = is_hacked(os.path.join(root, filename)) + if hacked is not False and hacked['score'] >= MIN_SCORE: + results.append(hacked) + else: + filename = basedir + root = os.getcwd() + if fnmatch.fnmatch(filename, '*.php') or \ + fnmatch.fnmatch(filename, '*.txt') or \ + fnmatch.fnmatch(filename, '*.js'): + hacked = is_hacked(os.path.join(root, filename)) + if hacked is not False and hacked['score'] >= MIN_SCORE: + results.append(hacked) + + + results.sort(key=lambda x: x['score'], reverse=True) + + if SERIALIZER == 'json': +# print(yaml.dump(results[0:MAX_RESULTS])) +# else: + print(json.dumps(results[0:MAX_RESULTS], indent=4)) + + if RESULT_POST: + import requests + import urllib + headers = { + "Content-type": "application/x-yaml; charset=utf-8", + "Accept": "text/plain" + } + if basedir[0] == '/': + path = urllib.quote_plus(basedir[1::]) + else: + path = urllib.quote_plus(basedir) + r = requests.post(RESULT_POST + '/' + path, + data=yaml.dump(results[0:MAX_RESULTS]), + headers=headers) + print(r) + print("=" * 100) + print(r.text) + print("URL : ", RESULT_POST) \ No newline at end of file diff --git a/scan.py b/scan.py index 972a201..1d910ca 100644 --- a/scan.py +++ b/scan.py @@ -300,7 +300,7 @@ def is_hacked(filename): if filename[0] != '/': filename = os.getcwd() + '/' + filename - return {'filename': filename[0:200], + return {'filename': filename.encode('utf-8'), 'score': total_score, 'mtime': os.stat(filename).st_mtime, 'ctime': os.stat(filename).st_ctime, @@ -611,7 +611,7 @@ def is_hacked(filename): if cleanup_available and line_num == 1: cleanup_available = False - return {'filename': filename[0:200], + return {'filename': filename, 'score': total_score, 'mtime': os.stat(filename).st_mtime, 'ctime': os.stat(filename).st_ctime, @@ -636,8 +636,7 @@ if __name__ == '__main__': import yaml SERIALIZER = 'yaml' except ImportError: - import json - + import json parser = argparse.ArgumentParser( description='Check directory or file for PHP malwares.') parser.add_argument('directory_file',