Initial public portfolio release
Sanitized version of red team infrastructure automation platform. Operational content (implant pipelines, lures, credential capture) replaced with documented stubs. Architecture and infrastructure automation code intact.
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
---
|
||||
# Advanced evasion techniques for red team phishing
|
||||
|
||||
- name: Install advanced evasion tools
|
||||
apt:
|
||||
name:
|
||||
- python3-dnspython
|
||||
- python3-requests
|
||||
- python3-selenium
|
||||
- chromium-browser
|
||||
- chromium-chromedriver
|
||||
- tor
|
||||
- proxychains4
|
||||
state: present
|
||||
|
||||
- name: Create SMTP smuggling configuration
|
||||
template:
|
||||
src: "../templates/smtp-smuggling.py.j2"
|
||||
dest: "/root/Tools/phishing/smtp-smuggling.py"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
when: enable_smtp_smuggling | default(false) | bool
|
||||
|
||||
- name: Configure SPF bypass techniques
|
||||
template:
|
||||
src: "../templates/spf-bypass.sh.j2"
|
||||
dest: "/root/Tools/phishing/spf-bypass.sh"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
when: enable_spf_bypass | default(false) | bool
|
||||
|
||||
- name: Create domain aging simulation
|
||||
template:
|
||||
src: "../templates/domain-aging.py.j2"
|
||||
dest: "/root/Tools/phishing/domain-aging.py"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
when: aged_domain_mode | default(false) | bool
|
||||
|
||||
- name: Set up MTA fronting configuration
|
||||
template:
|
||||
src: "../templates/mta-fronting.conf.j2"
|
||||
dest: "/etc/postfix/mta_fronting.cf"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
when: enable_mta_fronting | default(false) | bool
|
||||
notify: restart postfix
|
||||
|
||||
- name: Create file format manipulation tools
|
||||
copy:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
with_items:
|
||||
# Note: These files need to be created or paths need to be verified
|
||||
# - { src: "../files/pdf-weaponizer.py", dest: "/root/Tools/phishing/pdf-weaponizer.py" }
|
||||
# - { src: "../files/office-macro-generator.py", dest: "/root/Tools/phishing/office-macro-generator.py" }
|
||||
# - { src: "../files/lnk-generator.py", dest: "/root/Tools/phishing/lnk-generator.py" }
|
||||
[]
|
||||
|
||||
- name: Create Living off the Land (LOtL) payload templates
|
||||
template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
with_items:
|
||||
- { src: "../templates/lotl-powershell.ps1.j2", dest: "/root/Tools/phishing/templates/lotl-powershell.ps1" }
|
||||
- { src: "../templates/lotl-wmic.cmd.j2", dest: "/root/Tools/phishing/templates/lotl-wmic.cmd" }
|
||||
- { src: "../templates/lotl-bitsadmin.cmd.j2", dest: "/root/Tools/phishing/templates/lotl-bitsadmin.cmd" }
|
||||
|
||||
- name: Set up CDN abuse configuration
|
||||
template:
|
||||
src: "../templates/cdn-abuse.py.j2"
|
||||
dest: "/root/Tools/phishing/cdn-abuse.py"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
when: enable_cdn_abuse | default(false) | bool
|
||||
|
||||
- name: Create domain reputation monitoring
|
||||
template:
|
||||
src: "../templates/reputation-monitor.py.j2"
|
||||
dest: "/root/Tools/phishing/reputation-monitor.py"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Set up cron job for reputation monitoring
|
||||
cron:
|
||||
name: "Domain reputation monitoring"
|
||||
minute: "0"
|
||||
hour: "*/4"
|
||||
job: "/root/Tools/phishing/reputation-monitor.py >> /root/Tools/phishing/logs/reputation.log 2>&1"
|
||||
|
||||
- name: Create email header spoofing tools
|
||||
template:
|
||||
src: "../templates/header-spoofing.py.j2"
|
||||
dest: "/root/Tools/phishing/header-spoofing.py"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Configure Tor for anonymization
|
||||
template:
|
||||
src: "../templates/torrc-phishing.j2"
|
||||
dest: "/etc/tor/torrc"
|
||||
backup: yes
|
||||
notify: restart tor
|
||||
when: enable_tor_routing | default(false) | bool
|
||||
|
||||
- name: Create user-agent rotation script
|
||||
template:
|
||||
src: "../templates/user-agent-rotation.py.j2"
|
||||
dest: "/root/Tools/phishing/user-agent-rotation.py"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Set up automated evasion techniques
|
||||
template:
|
||||
src: "../templates/automated-evasion.py.j2"
|
||||
dest: "/root/Tools/phishing/automated-evasion.py"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
when: enable_automated_evasion | default(false) | bool
|
||||
@@ -0,0 +1,344 @@
|
||||
---
|
||||
# Common tasks for configuring C2 server with Havoc C2 and EDR evasion
|
||||
# Shared across all providers
|
||||
|
||||
- name: Update apt cache
|
||||
apt:
|
||||
update_cache: yes
|
||||
|
||||
- name: Disable default Kali MOTD
|
||||
file:
|
||||
path: "{{ ansible_env.HOME }}/.hushlogin"
|
||||
state: touch
|
||||
mode: '0644'
|
||||
when: ansible_distribution == "Kali GNU/Linux"
|
||||
|
||||
- name: Set a custom MOTD
|
||||
template:
|
||||
src: "../../common/templates/motd.j2"
|
||||
dest: /etc/motd
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
|
||||
- name: Install base utilities and tools via apt
|
||||
apt:
|
||||
name:
|
||||
- git
|
||||
- wget
|
||||
- curl
|
||||
- unzip
|
||||
- python3-pip
|
||||
- python3-venv
|
||||
- tmux
|
||||
- pipx
|
||||
- nmap
|
||||
- tcpdump
|
||||
- hydra
|
||||
- john
|
||||
- hashcat
|
||||
- sqlmap
|
||||
- gobuster
|
||||
- dirb
|
||||
- enum4linux
|
||||
- dnsenum
|
||||
- seclists
|
||||
- responder
|
||||
- golang
|
||||
- proxychains
|
||||
- tor
|
||||
- crackmapexec
|
||||
- jq
|
||||
- build-essential
|
||||
- zip
|
||||
- unzip
|
||||
- postfix
|
||||
- net-tools
|
||||
- certbot
|
||||
- opendkim
|
||||
- opendkim-tools
|
||||
- dovecot-core
|
||||
- dovecot-imapd
|
||||
- dovecot-pop3d
|
||||
- dovecot-sieve
|
||||
- dovecot-managesieved
|
||||
- yq
|
||||
- build-essential
|
||||
# Additional Havoc C2 dependencies
|
||||
- mingw-w64
|
||||
- nasm
|
||||
- cmake
|
||||
- ninja-build
|
||||
- libfontconfig1
|
||||
- libglu1-mesa-dev
|
||||
- libgtest-dev
|
||||
- libspdlog-dev
|
||||
- libboost-all-dev
|
||||
- libncurses5-dev
|
||||
- libgdbm-dev
|
||||
- libssl-dev
|
||||
- libreadline-dev
|
||||
- libffi-dev
|
||||
- libsqlite3-dev
|
||||
- libbz2-dev
|
||||
- mesa-common-dev
|
||||
- qtbase5-dev
|
||||
- qtchooser
|
||||
- qt5-qmake
|
||||
- qtbase5-dev-tools
|
||||
- libqt5websockets5
|
||||
- libqt5websockets5-dev
|
||||
state: present
|
||||
|
||||
- name: Create directories for operational scripts
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: '0700'
|
||||
owner: root
|
||||
group: root
|
||||
with_items:
|
||||
- /root/Tools
|
||||
- /root/Tools/beacons
|
||||
- /root/Tools/payloads
|
||||
|
||||
- name: Copy operational scripts
|
||||
copy:
|
||||
src: "{{ item }}"
|
||||
dest: "/root/Tools/{{ item | basename }}"
|
||||
mode: '0700'
|
||||
owner: root
|
||||
group: root
|
||||
with_items:
|
||||
- "../../../common/files/clean-logs.sh"
|
||||
- "../../../common/files/secure-exit.sh"
|
||||
- "../files/havoc_installer.sh"
|
||||
- "../files/havoc_shell_handler.sh"
|
||||
- "../files/secure_payload_sync.sh"
|
||||
|
||||
- name: Copy post-install script
|
||||
copy:
|
||||
src: "../../../common/files/post_install_c2.sh"
|
||||
dest: "/root/Tools/post_install_c2.sh"
|
||||
mode: '0700'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Copy port randomization script
|
||||
copy:
|
||||
src: "../../../common/files/randomize_ports.sh"
|
||||
dest: "/root/Tools/randomize_ports.sh"
|
||||
mode: '0700'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Create post-install instructions
|
||||
template:
|
||||
src: "../../common/templates/POST_INSTALL_INSTRUCTIONS.txt.j2"
|
||||
dest: "/root/POST_INSTALL_INSTRUCTIONS.txt"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Set up systemd timer for payload sync
|
||||
shell: |
|
||||
cat > /etc/systemd/system/payload-sync.service << 'EOF'
|
||||
[Unit]
|
||||
Description=Secure Payload Sync Service
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/root/Tools/secure_payload_sync.sh
|
||||
User=root
|
||||
Group=root
|
||||
PrivateTmp=true
|
||||
StandardOutput=null
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
cat > /etc/systemd/system/payload-sync.timer << 'EOF'
|
||||
[Unit]
|
||||
Description=Secure Payload Sync Timer
|
||||
Requires=payload-sync.service
|
||||
|
||||
[Timer]
|
||||
OnBootSec=5min
|
||||
OnUnitActiveSec=30m
|
||||
RandomizedDelaySec=30m
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable payload-sync.timer
|
||||
systemctl start payload-sync.timer
|
||||
|
||||
- name: Install Havoc C2 Framework
|
||||
shell: "/root/Tools/havoc_installer.sh"
|
||||
args:
|
||||
creates: "/root/Tools/Havoc"
|
||||
async: 1800 # Allow 30 minutes for completion
|
||||
poll: 0 # Don't wait for completion
|
||||
register: havoc_installation_job
|
||||
|
||||
- name: Wait for Havoc installation to complete
|
||||
async_status:
|
||||
jid: "{{ havoc_installation_job.ansible_job_id }}"
|
||||
register: job_result
|
||||
until: job_result.finished
|
||||
retries: 60 # Check every 30 seconds for up to 30 minutes
|
||||
delay: 30
|
||||
when: havoc_installation_job is defined
|
||||
|
||||
- name: Display Havoc installation output
|
||||
debug:
|
||||
var: havoc_installation_result.stdout_lines
|
||||
when: havoc_installation_result.stdout_lines is defined
|
||||
|
||||
- name: Create Havoc payload generation script
|
||||
template:
|
||||
src: "../templates/generate_havoc_payloads.sh.j2"
|
||||
dest: "/root/Tools/generate_havoc_payloads.sh"
|
||||
mode: '0700'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Create Havoc C2 configuration from template
|
||||
template:
|
||||
src: "../templates/havoc-config.yaotl.j2"
|
||||
dest: "/root/Tools/Havoc/data/havoc.yaotl"
|
||||
mode: '0600'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Create Linux loader script template
|
||||
template:
|
||||
src: "../../common/templates/linux_loader.sh.j2"
|
||||
dest: "/root/Tools/linux_loader.template"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Create Windows PowerShell loader template
|
||||
template:
|
||||
src: "../../common/templates/windows_loader.ps1.j2"
|
||||
dest: "/root/Tools/windows_loader.template"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Create beacon server script from template
|
||||
template:
|
||||
src: "../templates/serve-havoc-payloads.sh.j2"
|
||||
dest: "/root/Tools/serve-havoc-payloads.sh"
|
||||
mode: '0700'
|
||||
owner: root
|
||||
group: root
|
||||
vars:
|
||||
redirector_subdomain: "{{ redirector_subdomain | default('cdn') }}"
|
||||
domain: "{{ domain }}"
|
||||
redirector_port: "{{ redirector_port | default('443') }}"
|
||||
|
||||
- name: Run port randomization if enabled
|
||||
include_tasks: port_randomization.yml
|
||||
when: randomize_ports | default(false) | bool
|
||||
|
||||
- name: Generate Havoc payloads
|
||||
shell: "/root/Tools/generate_havoc_payloads.sh"
|
||||
args:
|
||||
creates: "/root/Tools/Havoc/payloads/manifest.json"
|
||||
register: payload_generation_result
|
||||
environment:
|
||||
PATH: "{{ ansible_env.PATH }}:/usr/local/bin"
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Display payload generation output
|
||||
debug:
|
||||
var: payload_generation_result.stdout_lines
|
||||
when: payload_generation_result.stdout_lines is defined
|
||||
|
||||
- name: Start payload server
|
||||
shell: |
|
||||
nohup /root/Tools/serve-havoc-payloads.sh > /dev/null 2>&1 &
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: beacon_server_result
|
||||
|
||||
- name: Create NGINX configuration fragment for redirector
|
||||
template:
|
||||
src: "../../redirectors/templates/redirector-havoc-fragment.j2"
|
||||
dest: "/root/Tools/redirector-config.conf"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
vars:
|
||||
c2_ip: "{{ ansible_host }}"
|
||||
redirector_domain: "{{ redirector_subdomain }}.{{ domain }}"
|
||||
|
||||
- name: Create Havoc usage guide
|
||||
template:
|
||||
src: "../templates/havoc-guide.j2"
|
||||
dest: "/root/havoc-guide.txt"
|
||||
mode: '0600'
|
||||
owner: root
|
||||
group: root
|
||||
vars:
|
||||
c2_ip: "{{ ansible_host }}"
|
||||
redirector_domain: "{{ redirector_subdomain }}.{{ domain }}"
|
||||
|
||||
- name: Include traffic flow configuration
|
||||
include_tasks: "../../common/tasks/traffic_flow_config.yml"
|
||||
|
||||
- name: Set up cron job for log cleaning if zero-logs enabled
|
||||
cron:
|
||||
name: "Clean logs"
|
||||
minute: "0"
|
||||
hour: "*/6"
|
||||
job: "/root/Tools/clean-logs.sh > /dev/null 2>&1"
|
||||
when: zero_logs is defined and zero_logs | bool
|
||||
|
||||
- name: Ensure SSH key for redirector access is available
|
||||
block:
|
||||
- name: Copy deployment SSH key to C2 for redirector access (AWS)
|
||||
copy:
|
||||
src: "~/.ssh/c2deploy_{{ deployment_id }}.pem"
|
||||
dest: "/root/.ssh/redirector_key"
|
||||
mode: '0600'
|
||||
owner: root
|
||||
group: root
|
||||
when: provider == "aws"
|
||||
|
||||
- name: Copy deployment SSH key to C2 for redirector access (non-AWS)
|
||||
copy:
|
||||
src: "{{ ssh_key_path | replace('.pub', '') }}"
|
||||
dest: "/root/.ssh/redirector_key"
|
||||
mode: '0600'
|
||||
owner: root
|
||||
group: root
|
||||
when: provider != "aws"
|
||||
|
||||
- name: Create SSH config for redirector access
|
||||
blockinfile:
|
||||
path: /root/.ssh/config
|
||||
create: yes
|
||||
mode: '0600'
|
||||
owner: root
|
||||
group: root
|
||||
marker: "# {mark} ANSIBLE MANAGED REDIRECTOR CONFIG"
|
||||
block: |
|
||||
Host redirector
|
||||
HostName {{ redirector_ip }}
|
||||
User {{ ssh_user | default('root') }}
|
||||
IdentityFile /root/.ssh/redirector_key
|
||||
StrictHostKeyChecking no
|
||||
when: not redirector_only | bool and redirector_ip is defined
|
||||
|
||||
# Include integrated tracker tasks if requested
|
||||
- name: Include integrated tracker setup
|
||||
include_tasks: "configure_integrated_tracker.yml"
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
@@ -0,0 +1,150 @@
|
||||
---
|
||||
# Common task for configuring integrated email tracker on C2 server
|
||||
|
||||
- name: Check if integrated tracker setup is requested
|
||||
debug:
|
||||
msg: "Setting up integrated email tracker on C2 server"
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Install required packages for tracker
|
||||
apt:
|
||||
name:
|
||||
- python3-pip
|
||||
- python3-venv
|
||||
- python3-pillow
|
||||
- nginx
|
||||
- certbot
|
||||
- python3-certbot-nginx
|
||||
- jq
|
||||
state: present
|
||||
update_cache: yes
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Create tracker directory
|
||||
file:
|
||||
path: /root/Tools/tracker
|
||||
state: directory
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Create tracker data directory
|
||||
file:
|
||||
path: /root/Tools/tracker/data
|
||||
state: directory
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Create tracker system user
|
||||
user:
|
||||
name: tracker
|
||||
system: yes
|
||||
shell: /usr/sbin/nologin
|
||||
home: /root/Tools/tracker
|
||||
create_home: no
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Create Python virtual environment for tracker
|
||||
pip:
|
||||
virtualenv: /root/Tools/tracker/venv
|
||||
name:
|
||||
- flask
|
||||
- pillow
|
||||
- gunicorn
|
||||
virtualenv_command: /usr/bin/python3 -m venv
|
||||
environment:
|
||||
PATH: "/usr/local/bin:/usr/bin:/bin"
|
||||
vars:
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Copy tracker application code
|
||||
copy:
|
||||
src: "../../tracker/files/simple_email_tracker.py"
|
||||
dest: /root/Tools/tracker/simple_email_tracker.py
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Copy tracker statistics CLI tool
|
||||
copy:
|
||||
src: "../../tracker/files/tracker-stats.sh"
|
||||
dest: /root/Tools/tracker/tracker-stats.sh
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Copy systemd service file for tracker
|
||||
copy:
|
||||
src: "../../tracker/files/tracker.service"
|
||||
dest: /etc/systemd/system/tracker.service
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Set correct permissions for tracker directories
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: tracker
|
||||
group: tracker
|
||||
recurse: yes
|
||||
loop:
|
||||
- /root/Tools/tracker
|
||||
- /root/Tools/tracker/data
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Create NGINX site config for tracker
|
||||
template:
|
||||
src: "../../tracker/files/tracker-nginx.conf"
|
||||
dest: /etc/nginx/sites-available/tracker
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
vars:
|
||||
tracker_domain: "{{ tracker_domain | default('track.' + domain) }}"
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Enable tracker NGINX site
|
||||
file:
|
||||
src: /etc/nginx/sites-available/tracker
|
||||
dest: /etc/nginx/sites-enabled/tracker
|
||||
state: link
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Configure redirector to proxy tracking requests
|
||||
lineinfile:
|
||||
path: /etc/nginx/sites-available/default
|
||||
insertafter: "^\\s*location / {"
|
||||
line: " # Email tracker proxy path\n location /px/(.*)\\.png$ {\n proxy_pass http://{{ c2_ip }}:443/pixel/$1.png;\n proxy_set_header Host {{ tracker_domain }};\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-Proto https;\n }"
|
||||
delegate_to: "{{ groups['redirectors'][0] }}"
|
||||
when: setup_integrated_tracker | default(false) | bool and groups['redirectors'] is defined
|
||||
|
||||
- name: Set up SSL if requested
|
||||
shell: |
|
||||
certbot --nginx -d {{ tracker_domain }} --non-interactive --agree-tos -m {{ tracker_email }}
|
||||
args:
|
||||
creates: /etc/letsencrypt/live/{{ tracker_domain }}/fullchain.pem
|
||||
when: setup_integrated_tracker | default(false) | bool and tracker_setup_ssl | default(true) | bool
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Start and enable tracker service
|
||||
systemd:
|
||||
name: tracker
|
||||
state: started
|
||||
enabled: yes
|
||||
daemon_reload: yes
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
|
||||
- name: Add tracker alias to bashrc for easy access
|
||||
lineinfile:
|
||||
path: /root/.bashrc
|
||||
line: 'alias tracker="/root/Tools/tracker/tracker-stats.sh"'
|
||||
state: present
|
||||
when: setup_integrated_tracker | default(false) | bool
|
||||
Reference in New Issue
Block a user