Skip to content

Commit

Permalink
Fix Web backdoor false positive #8690
Browse files Browse the repository at this point in the history
  • Loading branch information
andresriancho committed Mar 16, 2015
1 parent e3dc1fc commit 447fc48
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 96 deletions.
119 changes: 52 additions & 67 deletions w3af/plugins/crawl/find_backdoors.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,39 +19,19 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
import re
import os

import w3af.core.controllers.output_manager as om
import w3af.core.data.constants.severity as severity
import w3af.core.data.kb.knowledge_base as kb

from w3af import ROOT_PATH
from w3af import CRAWL_PATH
from w3af.core.controllers.plugins.crawl_plugin import CrawlPlugin
from w3af.core.controllers.core_helpers.fingerprint_404 import is_404
from w3af.core.controllers.exceptions import BaseFrameworkException
from w3af.core.data.bloomfilter.scalable_bloom import ScalableBloomFilter
from w3af.core.data.kb.vuln import Vuln
from w3af.core.data.request.fuzzable_request import FuzzableRequest


# Mapping object to use in XPath search
BACKDOOR_COLLECTION = {'input': {'value': ('run', 'send', 'exec', 'execute',
'run cmd', 'execute command',
'run command', 'list', 'connect'),
'name': ('cmd', 'command')},
'form': {'enctype': ('multipart/form-data',)}}

# List of known offensive words.
KNOWN_OFFENSIVE_WORDS = {'access', 'backdoor', 'cmd', 'cmdExe_Click',
'cmd_exec', 'command', 'connect', 'directory',
'directories', 'exec', 'exec_cmd', 'execute', 'eval',
'file', 'file upload', 'hack', 'hacked', 'hacked by',
'hacking', 'htaccess', 'launch command',
'launch shell', 'list', 'listing', 'output', 'passwd',
'password', 'permission', 'remote', 'reverse', 'run',
'runcmd', 'server', 'shell', 'socket', 'system',
'user', 'userfile', 'userid', 'web shell', 'webshell'}
from w3af.core.data.esmre.multi_re import multi_re


class find_backdoors(CrawlPlugin):
Expand All @@ -60,14 +40,35 @@ class find_backdoors(CrawlPlugin):
:author: Andres Riancho (andres.riancho@gmail.com)
"""
WEBSHELL_DB = os.path.join(ROOT_PATH, 'plugins', 'crawl', 'find_backdoors',
'web_shells.txt')
WEBSHELL_DB = os.path.join(CRAWL_PATH, 'find_backdoors', 'web_shells.txt')
SIGNATURE_DB = os.path.join(CRAWL_PATH, 'find_backdoors', 'signatures.txt')

def __init__(self):
CrawlPlugin.__init__(self)

# Internal variables
self._analyzed_dirs = ScalableBloomFilter()
self._signature_re = None

def setup(self):
with self._plugin_lock:
if self._signature_re is not None:
return

signatures = self._read_signatures()
self._signature_re = multi_re(signatures, hint_len=2)

def _read_signatures(self):
for line in file(self.SIGNATURE_DB):
line = line.strip()

if not line:
continue

if line.startswith('#'):
continue

yield (line, 'Backdoor signature')

def crawl(self, fuzzable_request):
"""
Expand All @@ -82,6 +83,8 @@ def crawl(self, fuzzable_request):
if domain_path not in self._analyzed_dirs:
self._analyzed_dirs.add(domain_path)

self.setup()

# Read the web shell database
web_shells = self._iter_web_shells()

Expand Down Expand Up @@ -115,61 +118,43 @@ def _check_if_exists(self, web_shell_url):
except BaseFrameworkException:
om.out.debug('Failed to GET webshell:' + web_shell_url)
else:
if self._is_possible_backdoor(response):
desc = u'A web backdoor was found at: "%s"; this could' \
u' indicate that the server has been compromised.'
desc %= response.get_url()
signature = self._match_signature(response)
if signature is None:
return

v = Vuln(u'Potential web backdoor', desc, severity.HIGH,
response.id, self.get_name())
v.set_url(response.get_url())
desc = u'An HTTP response matching the web backdoor signature' \
u' "%s" was found at: "%s"; this could indicate that the' \
u' server has been compromised.'
desc %= (signature, response.get_url())

kb.kb.append(self, 'backdoors', v)
om.out.vulnerability(v.get_desc(), severity=v.get_severity())
# It's probability is higher if we found a long signature
_severity = severity.HIGH if len(signature) > 8 else severity.MEDIUM

fr = FuzzableRequest.from_http_response(response)
self.output_queue.put(fr)
v = Vuln(u'Potential web backdoor', desc, _severity,
response.id, self.get_name())
v.set_url(response.get_url())

def _is_possible_backdoor(self, response):
kb.kb.append(self, 'backdoors', v)
om.out.vulnerability(v.get_desc(), severity=v.get_severity())

fr = FuzzableRequest.from_http_response(response)
self.output_queue.put(fr)

def _match_signature(self, response):
"""
Heuristic to infer if the content of <response> has the pattern of a
backdoor response.
:param response: HTTPResponse object
:return: A bool value
"""
if is_404(response):
return False

body_text = response.get_body()
dom = response.get_dom()
if dom is not None:
for ele, attrs in BACKDOOR_COLLECTION.iteritems():
for attrname, attr_vals in attrs.iteritems():
# Set of lowered attribute values
dom_attr_vals = \
set(n.get(attrname).lower() for n in
(dom.xpath('//%s[@%s]' % (ele, attrname))))

# If at least one elem in intersection return True
if dom_attr_vals & set(attr_vals):
return True

# If no regex matched then try with keywords. At least 2 should be
# contained in 'body_text' to succeed.
#
# TODO: Improve this for loop so that we only read the response once and
# match all the known offensive words "at the same time", instead
# of reading the same string N times (once for each item in
# KNOWN_OFFENSIVE_WORDS)
times = 0
for back_kw in KNOWN_OFFENSIVE_WORDS:
if re.search(back_kw, body_text, re.I):
times += 1
if times == 2:
return True

return False

for match, _, _, _ in self._signature_re.query(body_text):
match_string = match.group(0)
return match_string

return None

def get_long_desc(self):
"""
Expand Down
80 changes: 80 additions & 0 deletions w3af/plugins/crawl/find_backdoors/signatures.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# https://sourceforge.net/projects/uniscan/
# https://code.google.com/p/bwsfinder/source/browse/trunk/

execute command: <input type="text" name="c">
jsp File browser
JspWebshell
PHPShell
phpRemoteView
JspSpy
<title>Mini Php Shell
<title>r57Shell
Simple CGI backdoor by DK
JFolder
NIX REMOTE WEB-SHELL
Execute Shell Command
PGTools
<title>PhpShell \d\.\d</title>
cmd\.jsp
c99shell</title>
hackru\.info
Hmilyld
c99shell
darkst
<title>\* ernealizm \* </title>
C99Shell v
xIXRbE
MyShell
fuckjp
yoco
backdoor\.jsp
<input type=text name=".CMD" size=45 value=
<title>ZoRBaCK Connect</title>
hkmjj
<title>ASPX Shell</title>
<title>.+- WSO.+</title>
hack520
BloodSword
Jsp File Admin
lcxMacros
JFileMan
<title>Knull Shell</title>
<title>:: www\.h4ckcity\.org :: Coded By 2MzRp & LocalMan ::</title>
<title>JSP Shell</title>
r57shell</title>
jspreverse\.html
icesword
<form method="POST" action="cfexec\.cfm">
JSP Backdoor Reverse Shell
REMVIEW TOOLS
PHVayv
JspDo
NetworkFileManagerPHP
<title>Stored Procedure Execute</title>
hacker666
JSP Backdoor
h4ntu
<title>SST Sheller !</title>
www\.hmilyld\.cn
heroes1412
safe\.yi\.org
<title>.+Ani-Shell.+</title>
AspRootkit
Persiangig
Webshell
<title>awen asp\.net webshell</title>
404 infiltrate team
pwnshell
websamba
xakep
PHP Shell
<title>SyRiAn Sh3ll
<title>.+NTDaddy.+</title>
<title>iTSecTeam</title>
PHANTASMA
<title>PHP-Terminal
jshell
lpl38
<FORM METHOD=GET ACTION='cmdjsp\.jsp'>
jsp web shell
Cod3rZ
32 changes: 3 additions & 29 deletions w3af/plugins/tests/crawl/test_find_backdoors.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class TestFindBackdoor(PluginTest):
target_url = 'http://%s/' % domain

MOCK_RESPONSES = [MockResponse('/', 'Hello world'),
MockResponse('/c99shell.php', 'cmd shell')]
MockResponse('/c99shell.php', '<html> c99shell</title>')]

def test_find_backdoor(self):
cfg = run_configs['base']
Expand Down Expand Up @@ -86,7 +86,8 @@ class TestFalsePositiveFindBackdoor2017_2(PluginTest):
APACHE_403 = get_apache_403('/forbidden/foobar', domain)

MOCK_RESPONSES = [MockResponse('/', '<a href="/forbidden/">403</a>'),
MockResponse('/forbidden/c99shell.php', 'cmd shell'),
MockResponse('/forbidden/c99shell.php',
'<HTML><title>c99shell</title>'),
MockResponse(re.compile('http://.*?/forbidden/.*'),
APACHE_403, status=403)]

Expand All @@ -98,30 +99,3 @@ def test_2017_false_positive_backdoor_2(self):

self.assertEqual(len(vulns), 1, vulns)


class TestFalsePositiveFindBackdoorNixWizardEmail(PluginTest):
target_url = 'http://httpretty-mock/'

MOCK_RESPONSES = [MockResponse('/cmd.php', '<input name="cmd"/>')]

def test_false_positive_backdoor_3(self):
cfg = run_configs['crawl']
self._scan(self.target_url, cfg['plugins'])

vulns = self.kb.get('find_backdoors', 'backdoors')

self.assertEqual(len(vulns), 1, vulns)


class TestFalsePositiveFindBackdoorNixWizardEmailNot(PluginTest):
target_url = 'http://httpretty-mock/'

MOCK_RESPONSES = [MockResponse('/cmd.php', '<input name="c1m2d"/>')]

def test_false_positive_backdoor_4(self):
cfg = run_configs['crawl']
self._scan(self.target_url, cfg['plugins'])

vulns = self.kb.get('find_backdoors', 'backdoors')

self.assertEqual(len(vulns), 0, vulns)

0 comments on commit 447fc48

Please sign in to comment.