{"id":437,"date":"2025-07-05T01:10:14","date_gmt":"2025-07-05T01:10:14","guid":{"rendered":"https:\/\/epbrtcybersecurityportfolio.xyz\/?p=437"},"modified":"2025-07-05T01:10:14","modified_gmt":"2025-07-05T01:10:14","slug":"zeek-script-part-2","status":"publish","type":"post","link":"https:\/\/epbrtcybersecurityportfolio.xyz\/?p=437","title":{"rendered":"Zeek Script Part 2"},"content":{"rendered":"\n<p><strong>Objectives<\/strong><\/p>\n\n\n\n<p><strong>Our main objectives in this section are to both learn more about how to write Zeek scripts by experimenting with the language and to create a script that will be useful in the real world.<\/strong><\/p>\n\n\n\n<p><strong>Exercise<\/strong><\/p>\n\n\n\n<p><strong>Description: In this exercise, we will create a script that reports outbound connections for which no previous DNS resolutions were observed.<\/strong><\/p>\n\n\n\n<p>First, for what reasons would a host ever attempt an outbound connection without first performing a DNS resolution? Here are some possibilities:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Hard-coded DNS server addresses<\/li>\n\n\n\n<li>Other hard-coded configuration of addresses<\/li>\n\n\n\n<li>Malware phoning home to a set of known addresses<\/li>\n\n\n\n<li>Resolution occurred over another path<\/li>\n\n\n\n<li>Outbound network scanners<\/li>\n\n\n\n<li>Malware attempting to spread out of our border<\/li>\n\n\n\n<li>Use of DNS over HTTPS or DNS over TLS<\/li>\n<\/ul>\n\n\n\n<p>There are surely other possibilities. However, the list above includes most of the more common explanations. Let&#8217;s discuss and explain these.<\/p>\n\n\n\n<p>We do expect outbound connections to the upstream DNS servers with no corresponding name queries. Why? That\u2019s just how it works! We statically configure our DNS server with a set of \u201croot hints\u201d or configure it to relay all requests upstream to a specific name server. We will never see queries related to finding these addresses.<\/p>\n\n\n\n<p>We may have systems or services that have hard-coded IP addresses in them. These are not necessarily nefarious, but such configurations are very fragile! It is much better to configure systems to connect to names that can be resolved via DNS. This allows us to relocate systems or services without breaking anything.<\/p>\n\n\n\n<p>The other reasons are not so good. This behavior could be an indication of a malware infection. Perhaps it is attempting to establish a command-and-control link to a static address or addresses. This is especially common when we are early on in a targeted compromise. Perhaps it\u2019s generic malware that\u2019s attempting to spread beyond the borders of our network. Since it doesn\u2019t know exactly who it wants to infect, malware will frequently scan random addresses (without DNS lookups, of course!).<\/p>\n\n\n\n<p>As far as DNS resolutions occurring over some other path, consider both why and how this might be done. Certainly, someone can argue that it would be done for privacy, but within an enterprise network, how much \u201cprivate\u201d activity should there be? It is more likely that, in this case, it is being done to bypass content filtering controls, though it could also be inadvertent.<\/p>\n\n\n\n<p>A prime way that this could happen is through the use of DNS over TLS (port 853) or DNS over HTTPS (port 443). As you are likely aware, a number of browsers optionally support DNS over HTTPS today. While the script that we are creating will not allow us to see what the DNS resolutions were, we can certainly detect that a system seems to be circumventing our normal resolution infrastructure and take appropriate action to remediate the system or activity.<\/p>\n\n\n\n<p><strong>We can see that this is all interesting behavior. How can we find it?<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Using any editor of your choice that is installed on the VM, please create a script named anomalousOutbound.zeek that will print the address resolved every time a DNS A or DNS AAAA record is seen. <\/strong><\/li>\n<\/ol>\n\n\n\n<p>To begin, I should define a module name for my script. I am going to call my module a module NoDNS. This defines a namespace and moves everything contained in my script into that namespace.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"399\" height=\"105\" src=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-27.png\" alt=\"\" class=\"wp-image-445\" srcset=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-27.png 399w, https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-27-300x79.png 300w\" sizes=\"auto, (max-width: 399px) 100vw, 399px\" \/><\/figure>\n\n\n\n<p>Next, I need to determine which events I need to subscribe to using the Zeek Scripting protocol analyzers documentation for DNS. Looking at this documentation, there are two events that I am interested in:<\/p>\n\n\n\n<p>event dns_A_reply(c: connection, msg: dns_msg, ans: dns_answer, a: addr)<\/p>\n\n\n\n<p>and the IPv6 version:<\/p>\n\n\n\n<p>event dns_AAAA_reply(c: connection, msg: dns_msg, ans: dns_answer, a: addr)<\/p>\n\n\n\n<p>I need to create event handlers for these two events in my script and add a code block to each that will print the resolved address.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"703\" height=\"232\" src=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-29.png\" alt=\"\" class=\"wp-image-450\" srcset=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-29.png 703w, https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-29-300x99.png 300w\" sizes=\"auto, (max-width: 703px) 100vw, 703px\" \/><\/figure>\n\n\n\n<p>I am interested in printing the address resolved every time an A record or AAAA record is seen. The parameter &#8216;a: addr&#8217; holds the resolved IP address from the DNS response and is passed to the event handler when the event is triggered during Zeek\u2019s analysis of network traffic. This is exactly what I need to print. Let&#8217;s modify my script to account for this.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"703\" height=\"220\" src=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-30.png\" alt=\"\" class=\"wp-image-453\" srcset=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-30.png 703w, https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-30-300x94.png 300w\" sizes=\"auto, (max-width: 703px) 100vw, 703px\" \/><\/figure>\n\n\n\n<p>Let&#8217;s make sure that the output of this script is correct:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"473\" src=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-31-1024x473.png\" alt=\"\" class=\"wp-image-455\" srcset=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-31-1024x473.png 1024w, https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-31-300x139.png 300w, https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-31-768x355.png 768w, https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-31.png 1271w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>The next step is to add some sort of global variable that will keep track of the DNS addresses that have been resolved. I can add a global variable for this purpose and add all of the addresses to it. I am going to use the <code>zeek_done()<\/code> event to print the content of this variable when the script completes to verify that it is working.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"700\" height=\"367\" src=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-34.png\" alt=\"\" class=\"wp-image-460\" srcset=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-34.png 700w, https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-34-300x157.png 300w\" sizes=\"auto, (max-width: 700px) 100vw, 700px\" \/><\/figure>\n\n\n\n<p>The output from this script doesn\u2019t look much different, but it runs much differently. Rather than printing addresses as they are seen, the full list is printed at the very end. The entire list is contained within curly braces ({}), indicating that it is a set:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"367\" src=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-35-1024x367.png\" alt=\"\" class=\"wp-image-462\" srcset=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-35-1024x367.png 1024w, https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-35-300x108.png 300w, https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-35-768x275.png 768w, https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-35.png 1319w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>The final step is to remove the <code>zeek_done()<\/code> call and to cause our script to trigger every time a connection is seen to a responding host that was not seen in our set. I need to identify an event that will be generated every time a new connection or session is seen. Looking at the zeek documentation, there is the <code>event new_connection(c: connection)<\/code> that is perfect for my purposes. It is generated whenever a new stream is identified involving TCP, UDP, or ICMP.<\/p>\n\n\n\n<p>Now I need to add some output to the event, but only if the responding host isn\u2019t found in the set of resolved addresses. Both vectors and sets allow for the use of the &#8216;in&#8217; keyword to check for membership. <\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"714\" height=\"406\" src=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-38.png\" alt=\"\" class=\"wp-image-472\" srcset=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-38.png 714w, https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-38-300x171.png 300w\" sizes=\"auto, (max-width: 714px) 100vw, 714px\" \/><\/figure>\n\n\n\n<p>Let&#8217;s run this new script:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"865\" height=\"366\" src=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-39.png\" alt=\"\" class=\"wp-image-474\" srcset=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-39.png 865w, https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-39-300x127.png 300w, https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-39-768x325.png 768w\" sizes=\"auto, (max-width: 865px) 100vw, 865px\" \/><\/figure>\n\n\n\n<p>This script still has a few problems. I am seeing external to internal and internal to internal. I need to add a condition to the <code>new_connection()<\/code> event handler to ignore external to internal and internal to internal connections.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"714\" height=\"508\" src=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-40.png\" alt=\"\" class=\"wp-image-477\" srcset=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-40.png 714w, https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-40-300x213.png 300w\" sizes=\"auto, (max-width: 714px) 100vw, 714px\" \/><\/figure>\n\n\n\n<p><br>The very last thing that I will add to this script is an exception for certain approved external DNS servers (such as 8.8.8.8, which is Google&#8217;s DNS server). This is necessary because internal DNS resolvers often connect directly to external DNS servers without first performing a DNS lookup for their addresses. These addresses are typically configured in advance, so no DNS resolution happens beforehand. To avoid false positives, the script should exclude these approved servers from being flagged.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"733\" height=\"582\" src=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-41.png\" alt=\"\" class=\"wp-image-481\" srcset=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-41.png 733w, https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-41-300x238.png 300w\" sizes=\"auto, (max-width: 733px) 100vw, 733px\" \/><\/figure>\n\n\n\n<p><br>Let&#8217;s do a last test run :<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"164\" src=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-42-1024x164.png\" alt=\"\" class=\"wp-image-483\" srcset=\"https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-42-1024x164.png 1024w, https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-42-300x48.png 300w, https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-42-768x123.png 768w, https:\/\/epbrtcybersecurityportfolio.xyz\/wp-content\/uploads\/2025\/07\/image-42.png 1351w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><br><br><br><br><br><br><br><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Objectives Our main objectives in this section are to both learn more about how to write Zeek scripts by experimenting with the language and to create a script that will be useful in the real world. Exercise Description: In this exercise, we will create a script that reports outbound connections for which no previous DNS [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-437","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/epbrtcybersecurityportfolio.xyz\/index.php?rest_route=\/wp\/v2\/posts\/437","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/epbrtcybersecurityportfolio.xyz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/epbrtcybersecurityportfolio.xyz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/epbrtcybersecurityportfolio.xyz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/epbrtcybersecurityportfolio.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=437"}],"version-history":[{"count":5,"href":"https:\/\/epbrtcybersecurityportfolio.xyz\/index.php?rest_route=\/wp\/v2\/posts\/437\/revisions"}],"predecessor-version":[{"id":484,"href":"https:\/\/epbrtcybersecurityportfolio.xyz\/index.php?rest_route=\/wp\/v2\/posts\/437\/revisions\/484"}],"wp:attachment":[{"href":"https:\/\/epbrtcybersecurityportfolio.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=437"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/epbrtcybersecurityportfolio.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=437"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/epbrtcybersecurityportfolio.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=437"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}