June 17, 2018

LVM backup script

The script snapshots the volume, exports point in time snapshot to the encrypted file, archives it to save space and applies the retention on backup destination folder. Also CPU usage of gzip or dd process can be controlled by cpulimit tool.

#!/bin/bash

## Fill in the variables below.
## Volume Group Name
VG="/dev/VG-NAME/"
## Logical Volumes within VG
lvstobackup="LV1 LV2 ... LV3"
## Prefix for snapshots.
snapshotprefix="_TempSnapshot"
## Backup key file.
backupkey="/etc/backupkey"
## Backup destination.
backuppath="/backup/"

# Backup script start.
log="$(date +"%F_%H-%M") - Backup script started.\n"
log+="$(date +"%F_%H-%M") - Checking if cpulimit process is running.\n"
## Below "if fi" can be uncommented if you need to limit cpu usage of gzip process. cpulimit tool must be installed.
#log+="$(date +"%F_%H-%M") - Checking if cpulimit process is running.\n"
#if pgrep cpulimit > /dev/null
#then
#log+="$(date +"%F_%H-%M") - cpulimit process is running - PID $(pgrep cpulimit).\n"
#else
#log+="$(date +"%F_%H-%M") - cpulimit process is not running. Starting.\n"
#cpulimit --exe /bin/gzip --limit 50 -b
#log+="$(date +"%F_%H-%M") - cpulimit process started - PID $(pgrep cpulimit).\n"
#fi
## Sets variable to track total amount of backup data.
totaltransferred=0
## Sets variable to track script warnings.
warning="0"
## Starting firs loop to create snapshots for specified Logical Volumes.
## It checks if the same snapshot exists (from previous backup). Removes it and re-creates the new one.
for lvbackup in $lvstobackup
do
lvfull=$VG$lvbackup
snapshot=$lvbackup$snapshotprefix
snapfull=$VG$snapshot
log+="$(date +"%F_%H-%M") - Checking '$lvbackup' snapshots - '$snapshot'.\n"
check=$(lvs | grep -q $snapshot && echo "Found")
if [ ! -z "$check" ]
then
log+="$(date +"%F_%H-%M") - '$snapshot' snapshot exists. Removing.\n"
lvremove $snapfull -f
log+="$(date +"%F_%H-%M") - '$snapshot' removed.\n"
fi
log+="$(date +"%F_%H-%M") - Creating '$snapshot' for '$lvfull'.\n"
lvcreate -L10G -s -n $snapshot $lvfull
log+="$(date +"%F_%H-%M") - '$VG$snapshot' created. Checking.\n"
check=$(lvs | grep -q $snapshot && echo "Found")
if [ ! -z "$check" ]
then
log+="$(date +"%F_%H-%M") - '$VG$snapshot' snapshot found.\n"
lvstosnapshot+="$lvbackup "
else
log+="$(date +"%F_%H-%M") - '$snapshot' hasn't been created. Check host. Skipping. "WARNING"\n"
warning="1"
fi
done
log+="\n"
## Starting new look to actually backup the volumes. "cat" can be used instead of "dd".
## Also gzip can be changed to --best or --fast depending on performance. 
for lvtosnapshot in $lvstosnapshot
do
snapshot=$lvtosnapshot$snapshotprefix
snapfull=$VG$lvtosnapshot$snapshotprefix
log+="$(date +"%F_%H-%M") - '$VG$snapshot' - exporting, archiving and encrypting the snapshot.\n"
backupfile=$backuppath$lvtosnapshot"_"$(date +"%F_%H-%M")".gz.enc"
dd if=$snapfull | gzip | openssl enc -aes-256-cbc -pass file:$backupkey > $backupfile
filesize=$(du -m $backupfile | cut -f1)
log+="$(date +"%F_%H-%M") - Export job has been completed. Size (MB) = $filesize.\n" 
(( totaltransferred += $filesize ))
## Removes the snapshot. Below one can be commented if you need to keep the snapshot until next script run.
## Make sure "-L10G" is enough for your LV's.
lvremove $snapfull -f
done
## Apply a retention to the backup destination.
## In example below it removes all data in destination folder older than 7 days.
log+="$(date +"%F_%H-%M") - Removing all files in '$backuppath' older than 7 days.\n"
find $backuppath -mtime +7 -type f -delete
## Uncomment if you are using cpulimit in the script.
#log+="$(date +"%F_%H-%M") - Killing CPU limit process.\n"
#pkill cpulimit
## Below lines will create a log file in a destination folder.
## It will include the information about total size transferred as well as warning value.
log+="$(date +"%F_%H-%M") - Backup script ended. Warning code = $warning. Total transfered (MB) = $totaltransferred.\n"
logfile=$backuppath"$(date +"%F_%H-%M").log"
echo -e $log > $logfile

June 10, 2018

Debian 9 - Docker Guacamole MySQL

# Install Docker and dependencies.
apt-get install apt-transport-https ca-certificates curl gnupg2 software-properties-common zip -y
curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"
apt-get update
apt-get install docker-ce -y

# Download guacamole and mysql containers.
docker pull guacamole/guacd
docker pull guacamole/guacamole
docker pull mysql

# Start MySQL container. Change rootpassword to your own.
docker run --name mysql -e MYSQL_ROOT_PASSWORD=rootpassword -d mysql

# Prepare database initialization script.
docker run --rm guacamole/guacamole /opt/guacamole/bin/initdb.sh --mysql > initdb.sql
docker cp initdb.sql mysql:/

# Connect to mysql container and create a database. Change userpassword to your own.
docker exec -it mysql bash
mysql -u root -p
CREATE DATABASE guacamole_db;
CREATE USER 'guacamole_user' IDENTIFIED BY 'userpassword';
GRANT SELECT,INSERT,UPDATE,DELETE ON guacamole_db.* TO 'guacamole_user';
FLUSH PRIVILEGES;
quit;
cat initdb.sql | mysql -u root -p guacamole_db
exit

# Start guacd and guacamole containers.
docker run --name guacd -d guacamole/guacd
docker run --name guacamole --link guacd:guacd --link mysql:mysql -e MYSQL_DATABASE=guacamole_db -e MYSQL_USER=guacamole_user -e MYSQL_PASSWORD=userpassword -d -p 8080:8080 guacamole/guacamole

# Fix blank start page.
wget https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-5.1.46.zip
unzip mysql-connector-java-5.1.46.zip
cd mysql-connector-java-5.1.46/
docker cp mysql-connector-java-5.1.46.jar guacamole:/root/.guacamole/lib/
docker cp mysql-connector-java-5.1.46.jar guacamole:/opt/guacamole/mysql/
docker restart guacamole

# Now you are ready to connect to your Guacamole instance at http://SERVERIP:8080/guacamole/ with guacadmin/guacadmin credentials.

May 30, 2018

Exchange Online – Blocked (unlicensed) user appears in GAL

Issue: By default, when you unassign a license from Office 365 account using portal (Azure AD Connect is in place), it removes the Exchange Online mailbox. Thus, an account is removed from Global Address List (GAL). However, in some situation mailbox still appears in GAL. It happens when retention is assigned to the mailbox.

Resolution: Extend an Active Directory Schema by the latest Exchange Cumulative Update. No need to install Exchange on premises, simply run the command below from Exchange installation folder.
setup.exe /PrepareSchema /IAcceptExchangeServerLicenseTerms

Then start Azure AD Connect and Refresh directory schema. It will enable a synchronization of new Exchange attributes to the cloud. Then populate MailNickname attribute. Final step will be setting up msExchangeHiddenFromAddressList attribute.

May 22, 2018

Brute forcing LUKS protected device

# Encrypt test device. In my case it is 8GB flash drive.
root@hostname:~# fdisk -l
Disk /dev/sda: 7.5 GiB, 8015314944 bytes, 15654912 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

root@hostname:~# cryptsetup luksFormat /dev/sda

WARNING!
========
This will overwrite data on /dev/sda irrevocably.

Are you sure? (Type uppercase yes): YES
Enter passphrase:
Verify passphrase:
root@hostname:~# cryptsetup luksOpen /dev/sda test
Enter passphrase for /dev/sda:

# Create file system on mapped device.
root@hostname:~# mkfs.ext4 /dev/mapper/test
mke2fs 1.43.4 (31-Jan-2017)
Creating filesystem with 1956352 4k blocks and 489600 inodes
Filesystem UUID: 6a969f1b-862b-4dfa-9904-912305da4098
Superblock backups stored on blocks:
    32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632

Allocating group tables: done                           
Writing inode tables: done                            
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done

# Mount encrypted volume.
root@hostname:~# mount /dev/mapper/test /test/
root@hostname:~# df -h
Filesystem        Size  Used Avail Use% Mounted on
/dev/mapper/test  7.3G   34M  6.9G   1% /test

# Umount and close luks device.
root@hostname:~# umount /test
root@hostname:~# cryptsetup luksClose test

# Snap luks header.
root@hostname:~ # dd if=/dev/sda of=test.header bs=512 count=4097
4097+0 records in
4097+0 records out
2097664 bytes (2.1 MB, 2.0 MiB) copied, 0.139339 s, 15.1 MB/s

# Copy the header to crack station and start brute forcing. In my case it is VM with Xeon CPU.
F:\hashcat-4.1.0>hashcat64.exe -m 14600 F:\test.header -a 3 ?d?d?d?d?d?d
hashcat (v4.1.0) starting...

OpenCL Platform #1: Intel(R) Corporation
========================================
* Device #1: Intel(R) Xeon(R) CPU D-1521 @ 2.40GHz, 1021/4087 MB allocatable, 8MCU

Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates

Applicable optimizers:
* Zero-Byte
* Single-Hash
* Single-Salt
* Brute-Force
* Slow-Hash-SIMD-LOOP

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256

Watchdog: Hardware monitoring interface not found on your system.
Watchdog: Temperature abort trigger disabled.

F:\test.header:123456

Session..........: hashcat
Status...........: Cracked
Hash.Type........: LUKS
Hash.Target......: F:\test.header
Time.Started.....: Tue May 22 12:33:39 2018 (23 secs)
Time.Estimated...: Tue May 22 12:34:02 2018 (0 secs)
Guess.Mask.......: ?d?d?d?d?d?d [6]
Guess.Queue......: 1/1 (100.00%)
Speed.Dev.#1.....:       89 H/s (8.62ms) @ Accel:256 Loops:128 Thr:1 Vec:8
Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts
Progress.........: 2048/1000000 (0.20%)
Rejected.........: 0/2048 (0.00%)
Restore.Point....: 0/100000 (0.00%)
Candidates.#1....: 123456 -> 135500
HWMon.Dev.#1.....: N/A

# Another example with custom charset.
F:\hashcat-4.1.0>hashcat64.exe -m 14600 F:\test.header -a 3 -1 123456 ?1?1?1?1?1?1
hashcat (v4.1.0) starting...

F:\test.header:123456

Session..........: hashcat
Status...........: Cracked
Hash.Type........: LUKS
Hash.Target......: F:\test.header
Time.Started.....: Tue May 22 12:44:57 2018 (22 secs)
Time.Estimated...: Tue May 22 12:45:19 2018 (0 secs)
Guess.Mask.......: ?1?1?1?1?1?1 [6]
Guess.Charset....: -1 123456, -2 Undefined, -3 Undefined, -4 Undefined
Guess.Queue......: 1/1 (100.00%)
Speed.Dev.#1.....:       91 H/s (8.36ms) @ Accel:256 Loops:128 Thr:1 Vec:8
Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts
Progress.........: 2048/46656 (4.39%)
Rejected.........: 0/2048 (0.00%)
Restore.Point....: 0/7776 (0.00%)
Candidates.#1....: 123456 -> 115144
HWMon.Dev.#1.....: N/A

May 21, 2018

Convert MBR to GPT without data loss

It is possible to convert MBR to GPT disk using standard Windows Disk Management tool. However, it does require no volumes on it. Otherwise "Convert to GPT Disk" will be greyed out.

In this case you can use gptgen tool, which allows online disk conversion without the need to destroy the volume and its data. First of all use Diskpart tool to identify disk number. In my case it is Disk 2.

Then run gptgen tool with the following parameters “gptgen.exe -w \\.\physicaldrive2”, where 2 is the disk number from diskpart tool.

After that run Diskpart to check if the settings are applied successfully.

May 11, 2018

Active Directory - Password Reset Delegation Audit using PowerShell

I have Test OU within example.com Active Directory. PWResetUser was delegated password reset permissions on that OU. However, delegated user can’t reset passwords for all accounts inside specified unit due to access denied errors. The PowerShell script below helps to identify the problematic user accounts.
./check-acl.ps1 -Identity "*PWResetUser" -OU "OU=Test,DC=example,DC=com"

Rights         User         Type Identity
------         ----         ---- --------
Reset Password Username1   Allow TEST\PWResetUser
Reset Password Username2   Allow TEST\PWResetUser
No Data        Username3 No Data No Data

As you can see Username3 has No Data. After checking security tab of user properties, I found the problem. It was due to disabled security inheritance on that user.

So after reenabling inheritance (or manually delegating password reset permission for PWResetUser), delegated user can reset password for Username3 as well. The report now looks better.
Rights         User       Type Identity
------         ----       ---- --------
Reset Password Username1 Allow TEST\PWResetUser
Reset Password Username2 Allow TEST\PWResetUser
Reset Password Username3 Allow TEST\PWResetUser

Script Syntax: