Skip to content

Commit

Permalink
Add module http_post
Browse files Browse the repository at this point in the history
Check if credentials are transported over an encrypted channel
  • Loading branch information
OussamaBeng committed Jul 26, 2021
1 parent 26d16d0 commit 493bcd5
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 3 deletions.
1 change: 1 addition & 0 deletions make_exe.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def build_file_list(results, dest, root, src=""):
"wapitiCore.attack.mod_methods",
"wapitiCore.attack.mod_nikto",
"wapitiCore.attack.mod_permanentxss",
"wapitiCore.attack.mod_http_post",
"wapitiCore.attack.mod_redirect",
"wapitiCore.attack.mod_shellshock",
"wapitiCore.attack.mod_sql",
Expand Down
116 changes: 116 additions & 0 deletions tests/attack/test_mod_http_post.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from unittest.mock import Mock
from asyncio import Event

import respx
import httpx
import pytest

from wapitiCore.net.web import Request
from wapitiCore.net.crawler import AsyncCrawler
from wapitiCore.attack.mod_http_post import mod_http_post
from wapitiCore.language.vulnerability import _
from tests import AsyncMock


@pytest.mark.asyncio
@respx.mock

async def test_no_login_form():
respx.get("http://perdu.com/").mock(
return_value=httpx.Response(
200,
text="<html><head><title>Vous Etes Perdu ?</title></head><body><h1>Perdu sur l'Internet ?</h1> \
<h2>Pas de panique, on va vous aider</h2> \
<strong><pre> * <----- vous &ecirc;tes ici</pre></strong></body></html>"
)
)
persister = AsyncMock()

request = Request("http://perdu.com/")
request.path_id = 1
# persister.requests.append(request)

crawler = AsyncCrawler("http://perdu.com/")

options = {"timeout": 10, "level": 2}
logger = Mock()

module = mod_http_post(crawler, persister, logger, options, Event())
module.verbose = 2

await module.attack(request)

assert not persister.add_payload.call_count
await crawler.close()

@pytest.mark.asyncio
@respx.mock
async def test_login_form_https():
url = "https://perdu.com/"
body = """<html>
<body>
<form method="POST">
<input type="text" name="username" />
<input type="password" name="pass" />
</form>
</body>
</html>
"""

respx.get(url).mock(return_value=httpx.Response(200, text=body))

persister = AsyncMock()
request = Request("https://perdu.com/")
request.path_id = 1
# persister.requests.append(request)

crawler = AsyncCrawler("https://perdu.com/")

options = {"timeout": 10, "level": 2}
logger = Mock()

module = mod_http_post(crawler, persister, logger, options, Event())
module.verbose = 2

await module.attack(request)
assert not persister.add_payload.call_count
await crawler.close()

@pytest.mark.asyncio
@respx.mock
async def test_login_form_http():
url = "http://perdu.com/"
body = """<html>
<body>
<form method="POST">
<input type="text" name="username" />
<input type="password" name="pass" value="Letm3in_" />
</form>
</body>
</html>
"""

respx.get(url).mock(return_value=httpx.Response(200, text=body))

persister = AsyncMock()
request = Request(
"http://perdu.com/",
method="POST",
post_params=[["email", "wapiti2021@mailinator.com"], ["password", "Letm3in_"]],
)
request.path_id = 1
# persister.requests.append(request)

crawler = AsyncCrawler("http://perdu.com/")

options = {"timeout": 10, "level": 2}
logger = Mock()

module = mod_http_post(crawler, persister, logger, options, Event())
module.verbose = 2

await module.attack(request)
assert persister.add_payload.call_count
assert persister.add_payload.call_args_list[0][1]["module"] == "http_post"
assert persister.add_payload.call_args_list[0][1]["category"] == _("POST HTTP")
await crawler.close()
3 changes: 2 additions & 1 deletion wapitiCore/attack/attack.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
"mod_redirect",
"mod_xxe",
"mod_wapp",
"mod_wp_enum"
"mod_wp_enum",
"mod_http_post"
]

# Modules that will be used if option -m isn't used
Expand Down
32 changes: 32 additions & 0 deletions wapitiCore/attack/mod_http_post.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from httpx import RequestError

from wapitiCore.attack.attack import Attack
from wapitiCore.net.web import Request
from wapitiCore.language.vulnerability import MEDIUM_LEVEL, _
from wapitiCore.definitions.http_post import NAME


# This module check the security of transported credentials of login forms
class mod_http_post(Attack):
"""Check if credentials are transported on an encrypted channel."""
name = "http_post"

async def must_attack(self, request: Request):
# We leverage the fact that the crawler will fill password entries with a known placeholder
if "https://" in request.url:
return False

return True

async def attack(self, request: Request):

if "Letm3in_" not in request.encoded_data + request.encoded_params:
return
self.finished = True

await self.add_vuln_medium(
request_id=request.path_id,
category=NAME,
request=request,
info=_("Credentials transported over an Unencrypted Channel on : {0}").format(request.url)
)
31 changes: 31 additions & 0 deletions wapitiCore/definitions/http_post.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from wapitiCore.language.language import _

TYPE = "vulnerability"
NAME = _("POST HTTP")
SHORT_NAME = NAME

DESCRIPTION = _(
"The application configuration should ensure that SSL is used for all access controlled pages.\\n)."
) + " " + _(
"If an application uses SSL to guarantee confidential communication with client browsers, "
) + " " + _(
"the application configuration should make it impossible to view any access controlled page without SSL."
)

SOLUTION = _(
"Force the use of HTTPS for all authentication requests"
)

REFERENCES = [
{
"title": "OWASP: Insecure Transport",
"url": "https://owasp.org/www-community/vulnerabilities/Insecure_Transport"
},
{
"title": "Acunetix: Insecure Authentication",
"url": "https://owasp.org/www-project-mobile-top-10/2016-risks/m4-insecure-authentication"
}
]
3 changes: 3 additions & 0 deletions wapitiCore/language_sources/en.po
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ msgstr ""
"Permanent XSS vulnerability found in {0} by injecting the parameter {1} of "
"{2}"

msgid "Credentials transported over an Unencrypted Channel on : {0}"
msgstr "Credentials transported over an Unencrypted Channel on : {0}"

msgid "Warning: Content-Security-Policy is present!"
msgstr "Warning: Content-Security-Policy is present!"

Expand Down
3 changes: 3 additions & 0 deletions wapitiCore/language_sources/fr.po
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ msgstr ""
"Vulnérabilité de XSS permanent trouvée dans {0} via une injection dans la "
"paramètre {1} de {2}"

msgid "Credentials transported over an Unencrypted Channel on : {0}"
msgstr "Identifiants transportés sur un canal non chiffré sur : {0}"

msgid "Warning: Content-Security-Policy is present!"
msgstr "Avertissement: L'entête Content-Security-Policy est présent!"

Expand Down
6 changes: 4 additions & 2 deletions wapitiCore/net/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -908,8 +908,10 @@ def find_login_form(self):
username_field_idx.append(i)

elif input_type == "text" and (
any(field_name in input_name for field_name in ["mail", "user", "login", "name"]) or
any(field_id in input_id for field_id in ["mail", "user", "login", "name"])
any(field_name in input_name for field_name in \
["mail", "user", "login", "name", "id", "client", "nom"])
or any(field_id in input_id for field_id in \
["mail", "user", "login", "name", "id", "client", "nom"])
):
username_field_idx.append(i)

Expand Down

0 comments on commit 493bcd5

Please sign in to comment.