<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Eli Rizk on Medium]]></title>
        <description><![CDATA[Stories by Eli Rizk on Medium]]></description>
        <link>https://medium.com/@elirizk?source=rss-48eed76153f1------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*SBKF_6lIj26-yR1IJ1iMKw.png</url>
            <title>Stories by Eli Rizk on Medium</title>
            <link>https://medium.com/@elirizk?source=rss-48eed76153f1------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 23 May 2026 16:23:36 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@elirizk/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[How to build custom CrowdStrike integrations with Foundry apps]]></title>
            <link>https://medium.com/@elirizk/how-to-build-custom-crowdstrike-integrations-with-foundry-apps-c6810cbca040?source=rss-48eed76153f1------2</link>
            <guid isPermaLink="false">https://medium.com/p/c6810cbca040</guid>
            <category><![CDATA[integration]]></category>
            <category><![CDATA[crowdstrike]]></category>
            <category><![CDATA[cybersecurity]]></category>
            <category><![CDATA[api]]></category>
            <category><![CDATA[automation]]></category>
            <dc:creator><![CDATA[Eli Rizk]]></dc:creator>
            <pubDate>Mon, 02 Dec 2024 17:53:16 GMT</pubDate>
            <atom:updated>2024-12-08T10:20:17.695Z</atom:updated>
            <content:encoded><![CDATA[<h4>In this post, I will introduce the Falcon Foundry platform to beginners who would like to get started with building custom security integrations with CrowdStrike. I will also walk through developing a custom integration app with Zoho Desk using CrowdStrike Falcon Foundry, cybersecurity’s first-low-code application platform.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tpSCteG_8OYJgAJMW1x1kg.jpeg" /></figure><h3>Motivation</h3><p>In today’s rapidly evolving cybersecurity landscape, professionals are constantly challenged to keep up with the latest threats and technologies. Navigating multiple platforms and integrating various tools can be time-consuming and complex. <a href="https://www.crowdstrike.com/platform/next-gen-siem/falcon-foundry">CrowdStrike Falcon Foundry</a> offers a powerful solution by providing a low-code application platform (LCAP) that simplifies the development of custom cybersecurity integration apps. By leveraging this platform, cybersecurity experts can streamline their workflows, enhance their threat detection capabilities, and respond to incidents more efficiently. This blog post aims to guide you through the process of developing a custom app using CrowdStrike Falcon Foundry, empowering you to harness the full potential of this innovative platform to integrate with any other tool at hand.</p><h3><strong>Background</strong></h3><p>Foundry applications offer a range of capabilities that allow developers to incorporate a multitude of functionalities within their integrated applications such as storing data in collections, executing code in functions, integration with third-party APIs, and more. The table below describes the capabilities covered by Foundry applications. Note that I will only be covering API integration and function capabilities in this post.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/762/1*UfsIRYRWj7JWidBsvik7Pg.png" /></figure><h4><strong>Foundry Functions</strong></h4><p>Foundry functions allow developers to build custom business logic into the app and run it in the CrowdStrike cloud. Supported languages are Python 3.9 or later and Go 1.19 or later. CrowdStrike caps the execution timeout to 30 seconds. The function capability allows security analysts to execute any custom code and include it as a workflow action in Fusion SOAR. Some examples of logic that can be implemented include modifying variables, writing to collections, executing a LogScale query, and even sending custom HTTP requests.</p><h4><strong>API Integration</strong></h4><p>Foundry applications are also able to integrate with HTTP-based web services within Falcon using the OpenAPI specification. Once configured, the Falcon platform becomes able to interact with and orchestrate API requests as a Fusion SOAR workflow action. Each Falcon application is limited to one API host (one domain per app). This capability allows CrowdStrike users to integrate their security solution with any other service through 3rd party API requests even if the integration isn’t natively supported.</p><h4><strong>Falcon Platform</strong></h4><p>Foundry gives users two ways to build and manage custom apps:</p><ol><li><strong>Command Line Interface (CLI)</strong>: build locally and deploy from the command line</li><li><strong>UI-based tool (App builder)</strong>: build the application from the Falcon console (over the web)</li></ol><p>Note that not all capabilities are offered over CLI or the App builder. The figure below specifies which interface allows you to develop specific capabilities:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/531/1*_8guBjrUFNUiN1fRIWZErw.png" /></figure><p>In this post, I will be exclusively using the Foundry CLI to create, develop, and deploy the Foundry app. Note that the API integration created can be developed using the App builder over the web.</p><h3><strong>Quickstart</strong></h3><p>To get started with Foundry app development, you’ll need to install the Foundry CLI. On Windows, you can install it using Scoop:</p><pre>scoop bucket add foundry https://github.com/crowdstrike/scoop-foundry-cli.git<br>scoop install foundrybash</pre><p>For Linux and macOS users, install using Homebrew:</p><pre>brew tap crowdstrike/foundry-cli<br>brew install foundry</pre><p>Verify your installation by running:</p><pre>foundry version</pre><p>Once the Foundry CLI is installed, you will need to create a profile by logging in. This ensures you have the appropriate credentials to build the app on your CrowdStrike console. Run <em>foundry login</em> in your terminal. You should be redirected to a login page where you will set the appropriate permissions, name your credentials and hit authorize.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/450/1*pFkxoX_yLB9xpjrzIXOthQ.png" /></figure><p>Here’s an overview of useful commands:</p><ul><li><em>foundry apps create</em> — Create a new Foundry app</li><li><em>foundry apps deploy </em>— Deploy the Foundry app</li><li><em>foundry apps release</em> — Release the Foundry app</li></ul><p>Once release, you will have to install it through the App builder in the Falcon console which might require consenting to the permissions requested by the app.</p><h3><strong>Create a function</strong></h3><p>To create a function, run the following command:</p><pre>foundry functions create - name &lt;function name&gt; - description &lt;function description&gt; - handler-name &lt;handler-name&gt; -l &lt;language&gt; - handler-method &lt;handler-method&gt; - handler-path &lt;handler-path&gt;</pre><p>This will create a new function with filler code. To add environment variables, you can include the following in the manifest file:</p><pre>functions:<br>  environment_variables:<br>    variable_name: value</pre><p>If the function expects input data or returns data, specify the request and response schema as JSON schema files and include their path in the manifest file. If needed, also consider making the function available on Fusion SOAR (through the App builder).</p><h3><strong>Create an API integration</strong></h3><p>To create an API integration, you will need to create an OpenAPI specification file (either yourself or the published specification of the API you’re integrating with). This file will define the API endpoints, request and response schemas, and other details. Once you have the OpenAPI specification file, you can create the API integration using the following command:</p><pre>foundry api-integrations create</pre><p>This command will prompt you for the name of the API integration, a description and a path to a local file or URL for the openAPI specification file.</p><h3><strong>References</strong></h3><p><a href="https://falcon.crowdstrike.com/documentation/category/c3d64B8e/falcon-foundry">Falcon Foundry documentation</a> (You must be signed in to access the page)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c6810cbca040" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How I used FastAPI to automate phishing email handling in Microsoft Teams]]></title>
            <link>https://medium.com/@elirizk/how-i-used-fastapi-to-automate-phishing-email-handling-in-microsoft-teams-2875b3b17c4b?source=rss-48eed76153f1------2</link>
            <guid isPermaLink="false">https://medium.com/p/2875b3b17c4b</guid>
            <category><![CDATA[microsoft]]></category>
            <category><![CDATA[phishing]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[automation]]></category>
            <category><![CDATA[cybersecurity]]></category>
            <dc:creator><![CDATA[Eli Rizk]]></dc:creator>
            <pubDate>Mon, 02 Dec 2024 17:52:04 GMT</pubDate>
            <atom:updated>2024-12-06T23:25:56.769Z</atom:updated>
            <content:encoded><![CDATA[<h4>In this post, I will highlight how I used Python’s FastAPI to develop a custom phishing email handling functionality in Microsoft Teams using actionable message cards.</h4><h3><strong>Motivation</strong></h3><p>Email is one of the most prevalent attack vectors for malicious actors. An attacker only needs one unknowing user to fall for a phishing campaign to gain foothold access to company systems and cause significant damage. Consequently, cybersecurity and IT analysts must always be vigilant about suspicious emails. Many email security providers offer solutions to prevent suspicious emails from reaching users’ inboxes, such as Microsoft’s Defender for Office 365, Proofpoint, Mimecast, and Abnormal. However, these products can introduce the risk of false positives, where legitimate emails are undelivered or stuck in quarantine. This requires SOC analysts to regularly monitor and review quarantined emails to release any legitimate ones. This motivated me to build a simple API that alerts analysts when an email is quarantined and allows quick action on the alert, such as releasing or deleting the email, changing the status of the detection, and more.</p><h3><strong>Project Architecture</strong></h3><p>This project will require to develop a FastAPI app instance endpoint to handle our program’s logic and a Python job to continuously query Microsoft Defender for Office 365 for new quarantined emails.</p><p>Below is a high-level diagram of the project architecture:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/871/1*AdtM8WLqgbXkrk-3Zak-_g.png" /></figure><h4><strong>FastAPI setup</strong></h4><p>FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. It is designed to be easy to use and learn, while also providing the performance of <strong>asynchronous</strong> code. FastAPI is built on top of Starlette for the web parts and Pydantic for the data parts. It allows you to quickly create robust and production-ready APIs with automatic interactive documentation generated by Swagger UI and ReDoc.</p><p>To get started with FastAPI, you need to install it using pip:</p><pre>pip install fastapi<br>pip install uvicorn[standard]</pre><p>You can then create a simple FastAPI application:</p><pre>from fastapi import FastAPI<br><br>app = FastAPI()<br><br>@app.get(&quot;/&quot;)<br>def read_root():<br>    return {&quot;Hello&quot;: &quot;World&quot;}</pre><p>To run the application, use the following command:</p><pre>uvicorn main:app --reload</pre><p>This will start a development server and you can access the interactive API documentation at <em>http://127.0.0.1:8000/docs</em>.</p><p>Now that we have setup the FastAPI instance, we can start building our endpoints. We will start by creating the endpoint that sends a Microsoft Teams alert for an analyzed email object. To do so, we will use the Microsoft Graph <a href="https://learn.microsoft.com/en-us/graph/api/resources/security-analyzedemail?view=graph-rest-beta"><em>analyzedEmail</em></a> resource type to retrieve the email’s information followed by a Microsoft Teams webhook to send the message card.</p><p>To query the Microsoft Graph API, we need to register an application in Microsoft and generate client credentials. We can do so in <a href="https://portal.azure.com/#home">Azure</a> using the <em>App registrations</em> service. Make sure to give the application the appropriate application permission: <em>SecurityAnalyzedMessage.ReadWrite.All</em>. You can follow this <a href="https://learn.microsoft.com/en-us/azure/active-directory-b2c/microsoft-graph-get-started?tabs=app-reg-ga#register-management-application">post</a> in Microsoft to guide you in registering an application that uses Microsoft Graph API.</p><pre>from fastapi import HTTPException<br>import httpx<br><br>MICROSOFT_GRAPH_API_URL = &quot;https://graph.microsoft.com/beta/security/collaboration/analyzedEmails/&quot;<br><br>async def get_ms_token():<br>    tenant_id = os.environ[&quot;MICROSOFTGRAPH_TENANT_ID&quot;]<br>    client_id = os.environ[&quot;MICROSOFTGRAPH_CLIENT_ID&quot;]<br>    client_secret = os.environ[&quot;MICROSOFTGRAPH_CLIENT_SECRET&quot;]<br>    scope = &quot;https://graph.microsoft.com/.default&quot;<br>    async with httpx.AsyncClient() as client:<br>        token_result = await client.post(f&quot;https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token&quot;, data={<br>        &quot;grant_type&quot;: &quot;client_credentials&quot;,<br>        &quot;client_id&quot;: client_id,<br>        &quot;client_secret&quot;: client_secret,<br>        &quot;scope&quot;: scope<br>    }).json()<br>    if &#39;access_token&#39; not in token_result:<br>        raise Exception(f&quot;Error while getting access token: {json.dumps(token_result, indent=4)}&quot;)<br>    return token_result[&#39;access_token&#39;]<br><br>@app.get(&quot;/analyzedEmail/{analyzedEmailId}&quot;)<br>async def get_analyzed_email(analyzedEmailId: str):<br>    access_token = await get_ms_token()<br>    headers = {<br>        &quot;Authorization&quot;: f&quot;Bearer {access_token}&quot;<br>    }<br>    async with httpx.AsyncClient() as client:<br>        response = await client.get(f&quot;{MICROSOFT_GRAPH_API_URL}{analyzedEmailId}&quot;, headers=headers)<br>        if response.status_code != 200:<br>            raise HTTPException(status_code=response.status_code, detail=&quot;Error retrieving analyzed email&quot;)</pre><p>Once we have obtained the analyzed email resource object, we are ready to send an alert in Microsoft Teams. The alert will be sent in the format of a message card to allow semi-automated actions by the members of the Teams channel. This will greatly facilitate the need for analysts to jump back and forth through different consoles.</p><pre>API_ENDPOINT = &quot;&lt;enter your API URL endpoint here&gt;&quot;<br>WEBHOOK_URL = &quot;&lt;entern your Microsoft Teams webhook URL&gt;&quot;<br>TENANT_ID = os.environ[&quot;MICROSOFTGRAPH_TENANT_ID&quot;]<br><br>@app.post(&quot;/sendToTeams/analyzedEmail/{analyzedEmailId}&quot;)<br>async def send_analyzedEmail(analyzedEmailId: str):<br>    email_data = await get_email_data(analyzedEmailId)<br>    message_card = {<br>        &quot;@type&quot;: &quot;MessageCard&quot;,<br>        &quot;@context&quot;: &quot;http://schema.org/extensions&quot;,<br>        &quot;summary&quot;: &quot;Email Sent to Quarantine&quot;,<br>        &quot;themeColor&quot;: &quot;0076D7&quot;,<br>        &quot;sections&quot;: [<br>            {<br>                &quot;activityTitle&quot;: &quot;Email Sent to Quarantine&quot;,<br>                &quot;activitySubtitle&quot;: &quot;Microsoft Defender for Office 365 detected a suspicious email and sent it to quarantine&quot;,<br>                &quot;activityImage&quot;: &quot;https://www.hkmu.edu.hk/ito/wp-content/uploads/sites/10/2021/06/phishingicon1.jpg&quot;,<br>                &quot;facts&quot;: [<br>                    {&quot;name&quot;: &quot;Logged Timestamp&quot;, &quot;value&quot;: email_data[&quot;loggedDateTime&quot;]},<br>                    {&quot;name&quot;: &quot;Network Message ID&quot;, &quot;value&quot;: email_data[&quot;networkMessageId&quot;]},<br>                    {&quot;name&quot;: &quot;Email Subject&quot;, &quot;value&quot;: email_data[&quot;subject&quot;]},<br>                    {&quot;name&quot;: &quot;Recipient Address&quot;, &quot;value&quot;: email_data[&quot;recipientEmailAddress&quot;]},<br>                    {&quot;name&quot;: &quot;Sender Address&quot;, &quot;value&quot;: email_data[&quot;senderDetail&quot;][&quot;fromAddress&quot;]},<br>                    {&quot;name&quot;: &quot;Return Path&quot;, &quot;value&quot;: email_data[&quot;returnPath&quot;]},<br>                    {&quot;name&quot;: &quot;Policy&quot;, &quot;value&quot;: email_data[&quot;policy&quot;]},<br>                    {&quot;name&quot;: &quot;Latest Action&quot;, &quot;value&quot;: email_data[&quot;latestDelivery&quot;][&quot;action&quot;]},<br>                    {&quot;name&quot;: &quot;Policy&quot;, &quot;value&quot;: email_data[&quot;policy&quot;]},<br>                    {&quot;name&quot;: &quot;DMARC&quot;, &quot;value&quot;: email_data[&quot;authenticationDetails&quot;][&quot;dmarc&quot;]},<br>                    {&quot;name&quot;: &quot;DKIM&quot;, &quot;value&quot;: email_data[&quot;authenticationDetails&quot;][&quot;dkim&quot;]},<br>                    {&quot;name&quot;: &quot;SPF&quot;, &quot;value&quot;: email_data[&quot;authenticationDetails&quot;][&quot;senderPolicyFramework&quot;]},<br>                    {&quot;name&quot;: &quot;Sender IP Address&quot;, &quot;value&quot;: email_data[&quot;senderDetail&quot;][&quot;ipv4&quot;]},<br>                    {&quot;name&quot;: &quot;Message URLs&quot;, &quot;value&quot;: &quot;\n&quot;.join([url[&quot;url&quot;] for url in email_data[&quot;urls&quot;]])},<br>                    {&quot;name&quot;: &quot;Attachment File Hashes&quot;, &quot;value&quot;: &quot;\n&quot;.join([attachment[&quot;sha256&quot;] for attachment in email_data[&quot;attachments&quot;]])}<br>                ],<br>                &quot;markdown&quot;: True<br>            }<br>        ],<br>        &quot;potentialAction&quot;: [<br>            {<br>                &quot;@type&quot;: &quot;OpenUri&quot;,<br>                &quot;name&quot;: &quot;View Email&quot;,<br>                &quot;targets&quot;: [<br>                    {<br>                        &quot;os&quot;: &quot;default&quot;,<br>                        &quot;uri&quot;: f&quot;https://security.microsoft.com/emailentity?f=summary&amp;startTime={email_data[&#39;loggedDateTime&#39;]}&amp;endTime={email_data[&#39;loggedDateTime&#39;]}&amp;id={email_data[&#39;networkMessageId&#39;]}&amp;recipient={email_data[&#39;recipientEmailAddress&#39;]}&amp;tid={TENANT_ID}&quot;<br>                    }<br>                ]<br>            },<br>            {<br>                &quot;@type&quot;: &quot;HttpPOST&quot;,<br>                &quot;name&quot;: &quot;Release Email&quot;,<br>                &quot;target&quot;: f&quot;{API_ENDPOINT}/analyzedEmail/remediate&quot;,<br>                &quot;body&quot;: &quot;{\&quot;networkMessageId\&quot;:\&quot;&quot; + email_data[&quot;networkMessageId&quot;] + &quot;\&quot;, \&quot;recipientEmailAddress\&quot;:\&quot;&quot; + email_data[&quot;recipientEmailAddress&quot;] + &quot;\&quot;, \&quot;action\&quot;:\&quot;moveToInbox\&quot;}&quot;<br>            },<br>            {<br>                &quot;@type&quot;: &quot;HttpPOST&quot;,<br>                &quot;name&quot;: &quot;Delete Email&quot;,<br>                &quot;target&quot;: f&quot;{API_ENDPOINT}/analyzedEmail/remediate&quot;,<br>                &quot;body&quot;: &quot;{\&quot;networkMessageId\&quot;:\&quot;&quot; + email_data[&quot;networkMessageId&quot;] + &quot;\&quot;, \&quot;recipientEmailAddress\&quot;:\&quot;&quot; + email_data[&quot;recipientEmailAddress&quot;] + &quot;\&quot;, \&quot;action\&quot;:\&quot;hardDelete\&quot;}&quot;<br>            },<br>            {<br>                &quot;@type&quot;: &quot;HttpPOST&quot;,<br>                &quot;name&quot;: &quot;Move to Junk&quot;,<br>                &quot;target&quot;: f&quot;{API_ENDPOINT}/analyzedEmail/remediate&quot;,<br>                &quot;body&quot;: &quot;{\&quot;networkMessageId\&quot;:\&quot;&quot; + email_data[&quot;networkMessageId&quot;] + &quot;\&quot;, \&quot;recipientEmailAddress\&quot;:\&quot;&quot; + email_data[&quot;recipientEmailAddress&quot;] + &quot;\&quot;, \&quot;action\&quot;:\&quot;moveToJunk\&quot;}&quot;<br>            }<br>        ]<br>    }<br>    async with httpx.AsyncClient() as client:<br>        response = await client.post(WEBHOOK_URL, json=message_card)<br>        if response.status_code != 200:<br>            raise HTTPException(status_code=404, detail=&quot;Error sending message card to Teams&quot;)<br>    return {&quot;message&quot;: &quot;Message card sent to Teams successfully&quot;}</pre><p>Here’s a sample of how the message card will look like in Teams:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QFMIiCMLE_BfeVBhmVlPkg.png" /></figure><p>Once we have sent the alert to Teams, we also need to accept incoming POST requests sent via the user actions in the message card. We will implement a release, hard delete and move to junk functionalities.</p><pre>@app.post(&quot;/analyzedEmail/remediate&quot;)<br>async def email_action(networkMessageId: str, recipientEmailAddress: str, action: str):<br>    access_token = await get_ms_token()<br>    headers = {<br>        &quot;Authorization&quot;: f&quot;Bearer {access_token}&quot;,<br>        &quot;Content-Type&quot;: &quot;application/json&quot;<br>    }<br>    payload = {<br>        &quot;action&quot;: request.action,<br>        &quot;networkMessageId&quot;: request.networkMessageId,<br>        &quot;recipientEmailAddress&quot;: request.recipientEmailAddress<br>    }<br>    async with httpx.AsyncClient() as client:<br>        response = await client.post(f&quot;{MICROSOFT_GRAPH_API_URL}remediate&quot;, headers=headers, json=payload)<br>        if response.status_code != 200:<br>            raise HTTPException(status_code=response.status_code, detail=&quot;Error performing email action&quot;)<br>    return {&quot;message&quot;: f&quot;Email action &#39;{request.action}&#39; performed successfully&quot;}</pre><p>This concludes our FastAPI instance. Next, we will implement a python script that will run as a service to continuously poll the Microsoft Graph API for new quarantined emails and use our API endpoint to send the alert to Teams.</p><h4><strong>Python Job</strong></h4><p>We will write our Python service script to check analyzed emails that are stuck in quarantine every 5 minutes by manipulating the <em>startTime</em> and <em>endTime</em> query parameters. In practice, I have noticed that the Microsoft Graph API has some latency before showing analyzed emails, so instead of querying from the past 5 minutes, the code queries from the previous 15th to 10th minutes. For every quarantined email, the script calls our FastAPI endpoint to send the relevant detection to Teams.</p><pre>import httpx<br>import asyncio<br><br>async def main():<br>    while True:<br>        try:<br>            now = time.time()<br>            start_time = time.strftime(&quot;%Y-%m-%dT%H:%M:%SZ&quot;, time.gmtime(now - 5*60*2))<br>            end_time = time.strftime(&quot;%Y-%m-%dT%H:%M:%SZ&quot;, time.gmtime(now - 5*60*3))<br>            access_token = await get_ms_token()<br>            headers = {<br>                &quot;Authorization&quot;: f&quot;Bearer {access_token}&quot;<br>            }<br>            async with httpx.AsyncClient() as client:<br>                response = await client.get(f&quot;https://graph.microsoft.com/beta/security/collaboration/analyzedEmails?startTime={start_time}&amp;endTime={end_time}&quot;, headers=headers)<br>                response.raise_for_status()<br>                emails = response.json()[&quot;value&quot;]<br>                for email in emails:<br>                    if email[&quot;latestDelivery&quot;][&quot;location&quot;] == &quot;quarantine&quot;:<br>                        await send_to_teams(email[&quot;id&quot;])<br>        except Exception as e:<br>            print(f&quot;Error: {e}&quot;)<br>        await asyncio.sleep(300)<br><br>if __name__ == &quot;__main__&quot;:<br>    asyncio.run(main())</pre><h3><strong>Conclusion</strong></h3><p>By leveraging FastAPI and Microsoft Graph, we have created a powerful tool that enhances the efficiency of cybersecurity analysts. This solution not only automates the detection and alerting process but also provides actionable insights directly within Microsoft Teams. This integration reduces the need for analysts to switch between different platforms, allowing them to respond to threats more quickly and effectively. The project can be easily augmented to interact with a back-end database and offer more functionalities such as assigning a status, an analyst, tags or comments to a detection facilitating team collaboration against possible attacks. As cyber threats continue to evolve, having such automated and integrated systems in place is crucial for maintaining robust security postures. This project demonstrates the potential of combining modern web frameworks with cloud-based APIs to build scalable and responsive security solutions. I hope this guide inspires you to explore further possibilities with FastAPI and Microsoft Graph in your own projects.</p><p><strong>What do you think about this solution? Have you tried something similar? Connect with me on </strong><a href="https://www.linkedin.com/in/elirizk/"><strong>LinkedIn</strong></a><strong> and let me know!</strong></p><h3><strong>References</strong></h3><p><a href="https://learn.microsoft.com/en-us/graph/api/resources/security-analyzedemail?view=graph-rest-beta">Microsoft Graph analyzedEmail resource type</a></p><p><a href="https://learn.microsoft.com/en-us/azure/active-directory-b2c/microsoft-graph-get-started?tabs=app-reg-ga#register-management-application">Microsoft Graph app registration walkthrough</a></p><p><a href="https://github.com/OfficeDev/outlook-dev-docs/blob/main/docs/actionable-messages/message-card-reference.md">Actionable message card documentation</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2875b3b17c4b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Getting started with static code analysis using Joern]]></title>
            <link>https://medium.com/@elirizk/getting-started-with-static-code-analysis-using-joern-6311e611be91?source=rss-48eed76153f1------2</link>
            <guid isPermaLink="false">https://medium.com/p/6311e611be91</guid>
            <category><![CDATA[computer-science]]></category>
            <category><![CDATA[graph]]></category>
            <category><![CDATA[vulnerability]]></category>
            <category><![CDATA[static-code-analysis]]></category>
            <category><![CDATA[cybersecurity]]></category>
            <dc:creator><![CDATA[Eli Rizk]]></dc:creator>
            <pubDate>Sat, 10 Feb 2024 02:56:48 GMT</pubDate>
            <atom:updated>2024-10-12T09:26:05.357Z</atom:updated>
            <content:encoded><![CDATA[<h4>In this post we will learn the basics of static code analysis and how to use joern for analyzing an application’s source code. This post also appears on my personal website <a href="https://elirizk.me/projects/joern-intro/">here</a>.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/753/1*gu62oPz7RAqVP14g0LTJVg.png" /></figure><h3>Motivation</h3><p>Static code analysis tools have come a long way in the past decade. Multiple tools have been developed to run on top of application’s code to detect bugs, vulnerabilities, or general inefficiencies. For example, Spotbugs has tremendously increased in popularity due to its wide support of bug discovery for Java code. In contrast, Joern provides a general static code analysis tool that supports multiple languages, including C++, Java, and PHP.</p><h3>Code Property Graphs</h3><p>In order to extract the multiple properties of source code (e.g., variable names, data dependencies, function calls and definitions, etc…), we will have to obtain an Intermediate Representation (IR) of the code. IR is vastly used by compilers to transform high-level code to low-level machine instructions and perform different types of enhancements on the code before turning it into an executable. IR includes different types of graphs, mainly: Abstract Syntax Trees (AST), Control Flow Graph (CFG) and Data/Program Dependence Graph (DDG or PDG). Let’s talk about the details of each below.</p><p>We will use the following C++ example to illustrate the differences of each graph type:</p><pre>void foo()<br>{<br>  int x = source();<br>  if (x &lt; MAX)<br>  { <br>    int y = 2 * x;<br>    sink(y);<br>  }<br>}</pre><p>You can think of the source function as a way to obtain user input and the sink function as a sensitive function that needs to be safeguarded against malicious input (e.g., database query, printing a value on a webpage, etc…)</p><p>Parsing this code, we can obtain the AST, CFG, or PDG graphs. The AST will use the language semantics to fill the graph nodes while preserving a true representation of the original code semantics; it also maintains the order in which statements were originally written in the code. The CFG showcases how the execution of the code can move between statements (which aren’t necessarily next to each other in the original code). It is mainly useful for representing if-else statements, for and while loops, break statements, etc… For example, the execution of the C++ code above can jump from evaluating the if condition to exiting the program if the condition evaluates to False, this possible execution will be displayed in the CFG. The PDG will highlight any dependencies on the data between different variables, function calls, conditions, etc… For example, the variable definition <em>x</em> in the third line is used in the if condition and to define <em>y</em>, so data dependency edges will be added to display the flow of data from one statement to another. The AST, CFG, and PDG graphs are shown below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Iaiusbdj0hxRr4bm-s6jcw.png" /><figcaption>AST, CFG, PDG of the C++ code</figcaption></figure><p>While each code representation has its advantages, it is also limiting. For instance, ASTs provide a true representation of the code but it’s hard to find data dependencies between different statements. To resolve these conflicting issues, Code Property Graphs (CPG) were invented to merge these three different graphs which will greatly simplify traversing the code and accessing multiple properties we might seek for discovering bugs, vulnerabilities, or inefficiencies. The CPG graph of the C++ code is presented below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SfjPuMTfjDosx_nDKmAAhg.png" /><figcaption>CPG of the C++ code</figcaption></figure><h3>Joern</h3><p>Joern parses the application code to obtain a Code Property Graph and allows developers to customize their needs as graph traversal algorithms on top of this graph.</p><p>To install joern, you can execute the following code on a Linux / Ubuntu system:</p><pre>mkdir joern &amp;&amp; cd joern # optional<br>curl -L &quot;https://github.com/joernio/joern/releases/latest/download/joern-install.sh&quot; -o joern-install.sh<br>chmod u+x joern-install.sh<br>./joern-install.sh --interactive</pre><p>By default, joern will be installed at <em>~/bin/joern</em>, Once installed, you can run joern in a command prompt as follows:</p><pre>cd &lt;path_to_joern&gt;/joern/joern-cli<br>./joern</pre><p>Once the joern terminal is open, it will provide you with an interactive way to parse source code and traverse its graph. Joern is written in Scala (a variant of Java that supports object-oriented and functional programming), so the code that will be written in the joern terminal will be Scala code. While knowing Scala will allow you to write more complicated traversals within the joern terminal, you don’t need full fluency in Scala to be able to pass some useful traversal commands. You also have the option to export the graph as a neo4j graph (among other graph extensions) and write Python code on top of the graph to traverse it. For now, we will directly work inside the joern terminal which will greatly simplify graph traversals as it provides an extensive list of useful commands.</p><p>For example if the C++ code above was saved as <em>foo.c</em>, we can provide its path to the <em>importCode</em> function in joern to parse the code and obtain its CPG as follows.</p><pre>joern&gt; importCode(&quot;foo.c&quot;)</pre><p>Once done, we can obtain the nodes of interest. For example if we want all reference to the identifier <em>y</em>, we can run:</p><pre>cpg.identifier(&quot;y&quot;).toList</pre><p>This will output the following list of Identifier nodes. Notice how we have two nodes representing the identifier <em>y</em>, once when it was defined in line 6 and another when it is passed to the <em>sink</em> function in line 7.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/587/1*GM0jklNYHCi_vgyeINxRfg.png" /></figure><p>To obtain a Call node, we can use the <em>call</em> function in the same manner.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/545/1*9aRWfU2ZQVFk0bA9_jWifg.png" /></figure><p>In order to find if the source function ever reaches a sink function (and potentially patch or sanitize the input to prevent any vulnerabilities), we can use the <em>reachableByFlows</em> function which performs backward traversal from the sink function to the source.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/459/1*_Rr7rMO_z1F23tajXzqhPA.png" /><figcaption>Vulnerability path</figcaption></figure><p>As shown, we obtain a detailed data flow from the source function to the sink, which provides useful information for programmers if they need to prevent any malicious input to reach a sensitive function. As such, they might sanitize the input before it reaches the sensitive function.</p><h3>Conclusion</h3><p>In this post, we saw how code property graphs are used to store different properties of the source code. This allows modeling bugs, vulnerabilities, and inefficiencies as graph traversal algorithms which greatly simplifies the development of static code analysis tools. We also used the joern tool to parse application source code to its CPG representation and performed some interesting traversals provided by the tool. In the next post, we will look at developing more complicated graph traversal algorithms to model the complexities of correctly detecting different types of vulnerabilities.</p><h3>References</h3><p><a href="https://fabianyamaguchi.com/files/2014-ieeesp.pdf">Paper that introduced Code Property Graphs for static code analysis.</a></p><p><a href="https://docs.joern.io/installation/">Joern installation guide</a></p><p><a href="https://docs.joern.io/traversal-basics/">Joern traversal basic commands</a></p><p>This post is also available on <a href="https://elirizk.me/projects/joern-intro/">my personal website</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6311e611be91" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Exploratory Data Analysis for Predicting Migration Rate from Socio-economic Factors]]></title>
            <link>https://medium.com/@elirizk/exploratory-data-analysis-for-predicting-migration-rate-from-socio-economic-factors-1a28ae7422be?source=rss-48eed76153f1------2</link>
            <guid isPermaLink="false">https://medium.com/p/1a28ae7422be</guid>
            <category><![CDATA[immigration]]></category>
            <category><![CDATA[exploratory-data-analysis]]></category>
            <category><![CDATA[data-analysis]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Eli Rizk]]></dc:creator>
            <pubDate>Tue, 02 Jan 2024 05:14:06 GMT</pubDate>
            <atom:updated>2024-05-20T18:17:31.367Z</atom:updated>
            <content:encoded><![CDATA[<h4>In this post we will apply exploratory data analysis concepts to the problem of predicting a country’s net migration rate as a time series from its socio-economic factors. The code of the project can be found on <a href="https://github.com/elirizk/ML-for-Predicting-Migration">Github</a>. This post is also available on my <a href="https://elirizk.me/projects/eda-migration/">personal website</a> where the world maps are interactive.</h4><h3>Introduction</h3><p>In this project, we will perform data analysis followed by ML model fitting to predict a country’s net migration rate as a time series from socio-economic factors. The following are some useful definitions to keep in mind.</p><p><strong>Net migration rate</strong>: the difference between the number of migrants entering and those leaving a country in a year, per 1,000 midyear population (U.S. Census definition). If the rate is positive, it indicates more people leaving the country than entering it (net immigration rate), and if it is negative, it indicates more people entering the country than leaving it (net emigration rate).</p><p><strong>DALYs </strong>(Disability-Adjusted Life Years): One DALY represents the loss of the equivalent of one year of full health. DALYs for a disease or health condition are the sum of the years of life lost due to premature mortality (YLLs) and the years lived with a disability (YLDs) due to prevalent cases of the disease or health condition in a population (WHO).</p><p><strong>HDI </strong>(Human Development Index): A composite index measuring average achievement in three basic dimensions of human development: healthcare, education, and economic situation (UNDP).</p><p>Other socio-economic factors which include <strong>GDP</strong>, <strong>Life Expectancy</strong>, <strong>Inflation</strong>, <strong>Mortality</strong>, and <strong>Healthcare expenditure</strong> (collected from the World Bank) will also guide us in performing our data analysis and ML training.</p><p>The final dataset can be found here: <a href="https://github.com/elirizk/ML-for-Predicting-Migration/blob/master/Dataset.csv">https://github.com/elirizk/ML-for-Predicting-Migration/blob/master/Dataset.csv</a></p><h3>Feature Correlation</h3><p>At first, we have to look at the correlation of the different features to get a sense of how the features interact with each other, which will help guide us through the exploratory data analysis. After reading the dataset into a Pandas data frame, we obtain the following correlation matrix.</p><pre>import pandas as pd<br>df = pd.read_csv(&#39;Dataset.csv&#39;, header=0)<br>df.corr()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*7gIIVOSXU9Vh0GVN" /></figure><p>One one level, we can look at how the various features correlate with the net migration rate. The year variable has a near zero correlation with the net migration rate, which is a good aspect of our dataset. It points to the fact that the distribution of the net migration rate is independent of the year considered. However, the main factors correlated with the net migration rate are the Human Development Index (HDI), Mortality, and disability-adjusted life years (DALYs). The former indicator correlates positively with the migration while the two latter indicators correlate negatively. This makes sense as the higher the country is developed, the more immigrants might come into the country. While the higher the mortality and DALYs are, the more likely the country is to experience a loss of citizens through emigration. The other indicators, namely life expectancy, healthcare expenditure, and GDP seem to have limited correlation with the output feature. The inflation indicator has the lowest correlation (-0.5%) which might push us to eliminate the column altogether from the data. However, we still have to aggregate the data and visualize it to make that decision.</p><p>Another important aspect to note is how the input features correlate among each other. For example, the HDI and life expectancy seem to highly correlate (91%). In fact, one of the factors taken into consideration when calculating the HDI of a country by the UN is the life expectancy in that country. Hence, it makes total sense for these two indicators to correlate. However, we might have to remove one of them when training a machine learning model on the data, or alternatively merge the two columns. Unexpectedly also, mortality and DALYs are highly correlated (67%): they both indicate the development of the health sector in a country. The variables HDI and DALYs are also highly correlated (-80%). We will keep a close look at all the above features throughout the EDA so that we can conclude on whether to remove on of the above features or merge them together through dimensionality reduction at a later stage.</p><h3>Data Analysis with R</h3><p>We will perform the first part of our analysis using R. We will have to import the necessary library to visualize our plots (using <em>ggplot2</em>) and read our dataset. We will also transform the <em>Year</em> column into a Date type in R, divide the <em>HDI</em> from numerical to categorical (low, medium, high or very high), and remove any entry with unknown continent code.</p><pre>library(ggplot2)<br><br>setwd(&quot;dataset_directory&quot;)<br>df &lt;- read.csv(&quot;Dataset.csv&quot;, header=TRUE, na.strings = &quot;&quot;)<br><br>for (i in seq_len(length(df$Year))) {<br>  df$Year[i] &lt;- (paste(&quot;01-01-&quot;,as.character(df$Year[i]),sep=&quot;&quot;))<br>}<br><br>df$Year &lt;- as.Date(df$Year, format=&quot;%d-%m-%Y&quot;)<br>unique(df$Year)<br>sapply(df, class)<br><br>df$HDI &lt;- sapply(df$HDI, cut, breaks = c(0, 0.55, 0.7, 0.8, 1),<br>               labels = c(&quot;Low&quot;, &quot;Medium&quot;, &quot;High&quot;, &quot;Very High&quot;))<br>df &lt;- subset(df, df$Continent.Code!=&quot;Unknown&quot;)</pre><h4>Feature Distribution</h4><p>We will plot the distribution of some of the features throughout the years. The following R code plots the corresponding figures.</p><pre>ggplot(df, aes(x=Year, y=Net.Migration.Rate, group=Year)) +<br>  geom_boxplot() +<br>  coord_cartesian(ylim=c(-50,50)) +<br>  labs(title = &quot;Variation of Net Migration Rate per Year&quot;,<br>       y = &quot;Net Migration Rate&quot;, x = &quot;Year&quot;)<br><br>ggplot(df[df$DALYs&lt;150000,], aes(x=Year, y=DALYs, group=Year)) +<br>  geom_boxplot() +<br>  labs(title = &quot;Variation of DALYs per Year&quot;,<br>       y = &quot;DALYs&quot;, x = &quot;Year&quot;)<br><br>ggplot(df, aes(x=Year, y=GDP, group=Year)) +<br>  geom_boxplot() +<br>  coord_cartesian(ylim=c(-30,30)) +<br>  labs(title = &quot;Variation of GDP growth per Year&quot;,<br>       y = &quot;GDP growth (%)&quot;, x = &quot;Year&quot;)<br><br>ggplot(df, aes(x=Year, y=Inflation, group=Year)) +<br>  geom_boxplot() +<br>  coord_cartesian(ylim=c(-25,110)) +<br>  labs(title = &quot;Variation of Inflation per Year&quot;,<br>       y = &quot;Inflation, consumer prices (annual %)&quot;, x = &quot;Year&quot;)</pre><p><strong>Net Migration Rate per Year</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*wixVhqpRZ2wmoinF" /></figure><p>As we can see the median of this distribution remains roughly stable throughout the years: it stays around 0. It is also clear how there are a lot of outliers in this distribution. This is expected since a large positive migration rate in one country should translate into a negative migration rate in other countries (the immigrants of one are the emigrants of the other). The fact that the median is stable around 0 from 1960 till 2020 confirms the reliability of the data.</p><p><strong>DALYs</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*2JpQx_UK24SC3Fyg" /></figure><p>Concerning the boxplot of the DALYs, the data presents a decrease in the median and standard deviation of the DALYs with the progress of the years. This shows the overall increase in the quality of healthcare around the globe, which explains the decrease in the DALYs. The data has become more concentrated in recent years (decrease in the standard deviation) which might be due to globalization and recent efforts by the UN to better the living conditions of underdeveloped nations.</p><p><strong>GDP Growth</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*msNK18fshEle0dSE" /></figure><p>Concerning the boxplot of the GDP growth (as a percentage) per year, we can see the fluctuations of this feature throughout the years. The median fluctuates around 5% with occasional downfalls. We can point out specific years where the fall of the GDP growth was expected. For example, the US stock market crash of 2008, which affected most countries, caused a sharp decline of the GDP growth in that year. Additionally, the 2020 Coronavirus Stock Market Crash is also clearly visible in the boxplot where the median GDP growth of countries becomes negative for the first time since 1960, which indicates an overall decline in the GDP in most countries of the world due to the pandemic.</p><p><strong>Inflation</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*nODX4_xcn6fsRyc0" /></figure><p>Concerning inflation, it is striking to see that in early and later years (1960–1970 and 2000–2020) the median inflation fluctuates a bit above 0 and the standard deviation is smaller to the other periods. However from 1970 till 2000, we see that the data becomes more dispersed and that the median inflation is significantly larger than before 1970 or after 2000. This might be due to the fact that during this period, a lot of countries experienced political and economical crises, which skewed the data towards having a larger inflation. We can clearly see a large number of outliers in the data too, which confirms our hypothesis.</p><h4>Data Aggregation</h4><p>We will perform data aggregation according to the HDI level as well as the country’s continent.</p><p><strong>Aggregation by HDI Level</strong></p><p>First the UN divides the HDI into four levels: low, medium, high, and very high. After aggregating according to this classification, we can visualize the variation of the Net migration rate per year per HDI level using the R code below.</p><pre>agg1 &lt;- aggregate(cbind(Net.Migration.Rate) ~ HDI+Year, data=df, FUN=mean)<br><br>ggplot(agg1, aes(x=Year, y=Net.Migration.Rate, color=HDI)) +<br>  geom_line(stat=&quot;identity&quot;, lwd=1.2) +<br>  geom_smooth(linetype=2) +<br>  labs(title = &quot;Variation of Net Migration per Year&quot;,<br>       subtitle = &quot;Divided according to HDI&quot;,<br>       y = &quot;Net Migration Rate&quot;, x = &quot;&quot;)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*4luFUFG04ncuuzKE" /></figure><p>We can clearly see a distinction of this evolution according to the HDI level of the countries. As expected, the countries with a very high HDI have the greatest net migrant rate: a lot of immigrants come to highly developed countries. However, there is a decline in this rate starting in 2010. This might be due to stricter immigration policies.</p><p>We can also point out that countries who have a high HDI rank second in terms of migration rate. However, it is worthy to note that while this rate was positive in 1990, it decreased slowly to become negative in 2020. One hypothesis might be that some countries who had a high HDI in 1990 progressed and developed into having a very high HDI, climbing up the HDI ranking, while the remaining countries might have faced national difficulties preventing them from increasing their HDI. Hence, this decreased the overall average of the net migrant rate of high HDI countries (similar to what a sampling bias might do to the statistic).</p><p>Surprisingly, although countries with a low and medium HDI present a negative net migration rate, countries with a medium HDI have the lowest rate. Why aren’t countries with a low HDI with the lowest net migration rate? This might be due to the fact that extremely underdeveloped countries do not allow their citizens to emigrate freely from the country. For instance, African and Asian countries with very low HDI might suffer from conservative norms and low education and financial status, preventing them from immigration (such as the African tribes and Arab Bedouins) or it could even be a political regime prohibiting emigration.</p><p><strong>Aggregation by Continent</strong></p><p>Furthermore, we can split the data according to the six continents: Asia (AS), Africa (AF), Europe (EU), North America (NA), South America (SA), and Oceania (OC). The code below does this aggregation.</p><pre>agg2 &lt;- aggregate(cbind(Net.Migration.Rate) ~ Continent.Code+Year, data=df, FUN=mean)<br><br>ggplot(agg2[,], aes(x=Year, y=Net.Migration.Rate, color=Continent.Code)) +<br>  geom_line(stat=&quot;identity&quot;, lwd=1, alpha=0.5, linetype=1) +<br>  #facet_wrap(~ Continent.Code) +<br>  geom_smooth(linetype=2)+<br>  labs(title = &quot;Variation of Net Migration per Year&quot;,<br>       subtitle = &quot;Divided according to Continent&quot;,<br>       y = &quot;Net Migration Rate&quot;, x = &quot;Year&quot;)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*SO2OQipMzDSjr_jd" /></figure><p>We don’t see a strong correlation between the continents. Most continents have a negative migration rate, except for Asia. This might be due to the fact that most immigration happens between neighboring countries in the same continent. For instance, Syrians fleeing war to Lebanon, Venezuelans fleeing the inflation to Columbia, Mexicans immigrating to the USA, Ukrainians fleeing the Russian war to Poland and Moldova. All these examples strengthen our hypothesis that migration is concentrated among neighboring countries. Hence, the emigrants of a country get translated into immigrants for the neighboring country, in the same continent: resulting in an overall fluctuation of the net migration rate around 0 for most continents, especially in the past two decades i.e., from 2000 till 2020. We will try applying this theory when visualizing the migration rates through maps.</p><h4>Variation of the features in specific countries</h4><p>We will visualize the effect of inflation on net migration through the case study of a few countries in order to generalize on the interplay of the different features.</p><p><strong>Net migration and Inflation</strong></p><p>We will visualize the effect of inflation on net migration through their variation in two countries: Honduras, a country in Central America, and Iraq, a middle eastern country in Asia. The time series of this evolution can be shown below.</p><pre>countryName &lt;- &quot;Honduras&quot;<br>plot1 &lt;- ggplot(df[df$Country.Name==countryName,], aes(x=Year, y=Inflation)) +<br>  geom_line(stat=&quot;Identity&quot;) +<br>  geom_smooth() +<br>  labs(title = &quot;Variation of Net Migration and Inflation&quot;,<br>       y = &quot;Inflation, consumer prices (annual %)&quot;, x = &quot;&quot;, subtitle = countryName)<br><br>plot2 &lt;- ggplot(df[df$Country.Name==countryName,], aes(x=Year, y=Net.Migration.Rate)) +<br>  geom_line(stat=&quot;Identity&quot;) +<br>  geom_smooth() +<br>  labs(y = &quot;Net Migration Rate&quot;, x = &quot;Year&quot;)<br><br>countryName &lt;- &quot;Iraq&quot;<br>plot3 &lt;- ggplot(df[df$Country.Name==countryName,], aes(x=Year, y=Inflation)) +<br>  geom_line(stat=&quot;Identity&quot;) +<br>  geom_smooth() +<br>  labs(title = &quot;Variation of Net Migration and Inflation&quot;,<br>       y = &quot;Inflation, consumer prices (annual %)&quot;, x = &quot;&quot;, subtitle = countryName)<br><br>plot4 &lt;- ggplot(df[df$Country.Name==countryName,], aes(x=Year, y=Net.Migration.Rate)) +<br>  geom_line(stat=&quot;Identity&quot;) +<br>  geom_smooth() +<br>  labs(y = &quot;Net Migration Rate&quot;, x = &quot;Year&quot;)<br><br>gridExtra::grid.arrange(plot1, plot3, plot2, plot4,nrow = 2)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*XY3w9MSRm6cgs_3K" /></figure><p>The negative correlation between these two variables is clear: an increase in inflation correlates with a decrease in the net migration rate and vice versa in the two countries. When these countries experience a peak in inflation, it correlates with a significant decrease in the net migration i.e., an increase in the number of emigrants fleeing the country. When this inflation decreases, the net migration rate increases, signifying either that the emigrants are coming back to the country or that new immigrants are coming into the country due to the stabilization of the economical situation in this nation.</p><p><strong>Net migration and neighboring countries</strong></p><p>Furthermore, it is interesting to visualize the evolution of net migration in neighboring countries, especially those undergoing economical, financial or political crises. The graphs below shows the variation of the net migration for Venezuela &amp; Colombia, and for Mexico and the United States. As a historical background, we should consider that since 1970 Colombians have been fleeing to Venezuela to avoid the violent conflict of their homeland. However, as of 2016, the roles have been reversed: Venezuelans have been immigrating to Colombia because of the terrible financial crises of their country.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*yh9O39cvzaWu3kYe" /></figure><p>This trend is clearly shown in the data, whereas in early years the net migration of Colombia was negative but that of Venezuela positive. However, in recent years, the migration rate has been increasing in Colombia and decreasing in Venezuela (due to the mass immigration of Venezuelans to Colombia). The sharp decrease in this rate in Venezuela coincides with its spike in Colombia (around 2016–2020), which confirms our hypothesis.</p><p>While the situation in Colombia and Venezuela might be considered as an outlier, we can see a similar trend when neighboring countries have a huge disparity in economical or developmental opportunities. For instance, in Mexico and the United States, the variation of their net migration rate coincides with each other (whereas a sharp increase in one reflects a decrease in the other, especially around the year 2000).</p><p>A similar trend can be seen in other neighboring countries, e.g. Albania &amp; Greece, Bangladesh &amp; India, Syria &amp; Lebanon, Oman &amp; Yemen. The spike in the migration rate in one of these countries is correlated with a fall in its neighboring country around the same period. The widespread nature of this phenomena (which we can’t disregard as being a few outliers) will necessitate us to encode spatial locality in our model to consider the features of the neighboring countries (including the net migration rate) in order to predict the final migration rate of the given country.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*z9LlPQAwtemRfj9_" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*DUX8mSV8tuiOYYbM" /></figure><h3>Data Analysis with Python</h3><p>We will now plot the data as scatter plots and visualize it on maps using Python.</p><h4>Scatter Plots</h4><p>First, we will divide the countries into their HDI rank and plot them according to their net migration rate, GDP growth and Inflation percentage. The three dimensional scatter plot can be shown below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*Q0Rt2zIx1mKya9QJ" /></figure><p>As expected, the countries with a high net migration rate also happen to have a high HDI, a high GDP growth and a low inflation percentage, whereas countries with a negative migrant rate are mostly underdeveloped and suffer from a low GDP growth and a high inflation.</p><p>Next, we will visualize the scatter plot of net migration rate, DALYs, and mortality.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*6u1ht52w-rCw7glE" /></figure><p>Considering the high correlation of DALYs and mortality, we expect to see a clear distinction in the plot. As expected the countries with a high DALYs also have a high mortality and vice versa. We can clearly see in the plot that highly developed countries with low DALYs and mortality experience a high migrant rate whereas underdeveloped countries with high DALYs and mortality suffer from an increased number of emigration (negative net migration rate).</p><p>We are also able to animate this scatter plot by year to visualize the variation in those different features as a time series. You can refer to the code on GitHub to generate this scatter plot animation. Through this animation, we can learn about the yearly trends of the data. We notice that the DALYs and mortality tend to decrease from year to year and that the GDP growth fluctuates around 0 and 10% for most years except for its occasional fall during a market crash (e.g. 2008–2009 and 2020). Concerning the inflation, before 200 we can see a considerable dispersion of the data (indicating a large standard deviation) while after the year 2000 countries become closer in regards to the percentage of inflation. All these insights confirm what we deduced at the beginning from the boxplots of the distribution of the features in prior project section.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FpT3t_VJ2pdg%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DpT3t_VJ2pdg&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FpT3t_VJ2pdg%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/b7af1892617d43885e5de960f0fbb449/href">https://medium.com/media/b7af1892617d43885e5de960f0fbb449/href</a></iframe><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FpbR_hqgtync&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DpbR_hqgtync&amp;image=http%3A%2F%2Fi.ytimg.com%2Fvi%2FpbR_hqgtync%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/dfb7085617216fda460eee93e1fbee79/href">https://medium.com/media/dfb7085617216fda460eee93e1fbee79/href</a></iframe><h4>Animated Heatmap of Migration</h4><p>Considering the spatial and temporal features of our dataset, the best way to visualize the data would be through an animated world map. To do so, we will use the <em>geopandas</em> python module and merge our dataset with the world dataset available through the module. That way, the resulting dataset will include the appropriate country names and geometries so that it can be easily converted into a folium Map. Besides doing so, the <em>prepForHeatMap</em> function normalizes the net migration rate because the weight input of the <em>HeatMap</em> function must be between 0 and 1. The normalization disregards the outliers (with a z-score above 3 or below -3) when calculating the normal score, and then substitutes those with a z-score above 3 with a score of 1 and those below -3 with a score of 0. That way the outliers won’t skew the normal distribution while staying in the data (so that we don’t end up with missing countries on the map). Refer to the GitHub code for details about the implementation in the <em>GetMap.py</em> file.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F9Jl3xzE7DVE%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D9Jl3xzE7DVE&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F9Jl3xzE7DVE%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/701a4b4e0c04809340d562728865f5fe/href">https://medium.com/media/701a4b4e0c04809340d562728865f5fe/href</a></iframe><p>The purple countries are the ones with a low migration rate while the green ones have a high migration rate. This map gives us an overview of the migration flow in the year 2020. The most popular destinations for immigrants appear to be the US, Canada, Eastern Europe, Australia &amp; New Zealand (not shown in the figure) and the Arab states of the Persian Gulf (Saudi Arabia, Kuwait, UAE). There are a few outliers in the map, namely the countries of central America who are accepting Venezuelans immigrants suffering from the crisis of their country along with Lebanon who housed the Syrian immigrants fleeing war.</p><h4>Stacked Maps of the features</h4><p>We can also stack multiple features on a static <em>FoliumMap</em> by running the Jupyter notebook <em>GenerateMap.ipynb</em> on Github. After doing so, we end up with the folder <em>StackedMaps</em> filled with the static maps for every year from 1990 until 2020. We can select the specific feature we want to see its distribution throughout the world map. When we hover over the country, we can check the Migration rate of the country, which is implemented into the map to clarify the visualization of the data.</p><p>The below figure is the folium map of the year 2020 when “Net Migration Rate” is selected. The darker the country is, the bigger the migration rate and the lighter the color, the smaller the rate. As expected, the countries with a high migration rate are mostly the countries of Western Europe, North America, Australia, and the Arab gulf. There are a few countries in South America, Asia, and Africa suffering from a low migration rate. We will see if this data correlates with our other features by deselecting the migration rate and selecting the other features.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*QS-I8BLIC8xxwp-f" /><figcaption>Net Migration Rate</figcaption></figure><p>First, we will take a look at the distribution of inflation and its correlation with the above results. The figure below show the inflation and migration per country during the year 2008. The map confirms our earlier hypothesis: the larger the inflation, the lower the migration rate.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*F7pgV-Xot3QGyUye" /><figcaption>Inflation</figcaption></figure><p>For example, Argentina (the dark purple country in Southern America) who suffered from a large inflation in 2008 had a net migration rate of -0.625. The rest of the countries show a similar correlation.</p><p>The below figures show the distribution of the DALYs and mortality per country in 2008.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*zkgJKKS_VlN1PLso" /><figcaption>DALYs</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*gcBQS7kFU1W5Hjv6" /><figcaption>Mortality</figcaption></figure><p>As expected, countries suffering from a high mortality and high DALYs (like the Central African Republic, Afghanistan, Nigeria, etc…) also suffer from a low migration rate: a lot of emigrants are flying out of the country.</p><h3>Conclusion</h3><p>Throughout this project, we were able to clearly visualize how the different features interact with each other, guiding us in formulating a clear hypothesis before selecting and training a machine learning model. In the next part of this post, we will train various machine learning models and compare their performance as well as their explainability to reach a final recommendation as to how to predict a country’s net migration rate as a time series from socio-economic factors.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1a28ae7422be" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Integer Factorization]]></title>
            <link>https://medium.com/@elirizk/integer-factorization-8ea545cd6077?source=rss-48eed76153f1------2</link>
            <guid isPermaLink="false">https://medium.com/p/8ea545cd6077</guid>
            <category><![CDATA[number-theory]]></category>
            <category><![CDATA[complexity-analysis]]></category>
            <category><![CDATA[factorization]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[mathematics]]></category>
            <dc:creator><![CDATA[Eli Rizk]]></dc:creator>
            <pubDate>Sun, 24 Dec 2023 19:29:43 GMT</pubDate>
            <atom:updated>2024-05-20T18:19:51.383Z</atom:updated>
            <content:encoded><![CDATA[<h4>In this post, we will explore the problem of factorizing integers (in particular semi-primes, i.e., products of two primes). We will implement and contrast two different methods to factorize an integer: the brute-force way with the sieve of Eratosthenes and Pollard’s rho algorithm. This post is also available <a href="https://elirizk.me/projects/int-fact/">here</a>.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ItmibHAUpZLsijM8" /><figcaption>Photo by <a href="https://unsplash.com/@nhillier?utm_source=medium&amp;utm_medium=referral">Nick Hillier</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>The problem of integer factorization constitutes a fundamental problem in number theory and provides the security basis of multiple modern encryption schemes.</p><p>For example, the RSA asymmetric encryption standard for communication between two parties uses a (public key, private key) pair. In simple terms, RSA computes a large integer from the product of two large primes (<em>n = p.q</em>), and derives the public and private keys from the prime factors. In particular the public key <em>e</em> is chosen such that <em>gcd(e, (p-1).(q-1)) = 1</em> and the private key <em>d</em> is derived as <em>d</em> is the inverse of <em>e </em>modulo <em>(p-1).(q-1)</em>. The RSA schema assumes that <em>n</em> and <em>e</em> are public while the prime factors and the private key are kept secret. It’s easy to see from the formulas that given <em>n</em>’s prime factors and the public key, one can easily derive the private key. Hence, it is of utmost importance to the security of the algorithm that it is “hard” to factorize a large integer. Otherwise, the security of the schema is broken. Given that the basis of security of most asymmetric encryption standards (which are widely used in the world today as part of symmetric key exchange and certificate authority digital signature) is fundamentally based on the difficulty of integer factorization, a lot of research has been dedicated to improving the efficiency of integer factorization algorithms.</p><p>We will limit ourselves to the problem of factoring large numbers of the form: <em>n = p.q</em> where<em> p</em> and <em>q</em> are large prime numbers, which is the form used by RSA. Numbers that satisfy this form (a product of exactly two prime numbers) are called <strong>semi</strong>-<strong>primes</strong>.</p><p>Note that if we are able to decompose a number into the product of two other numbers, we can iterate this algorithm over the newly found numbers along with a primality check (which is in the order of <em>log(n)</em> ) to obtain the complete prime decomposition of any random integer <em>n</em>. Therefore, our above limitation can be easily expanded to solve the general problem of integer factorization. While primality testing can be achieved in the order of <em>log(n)</em>, no classical polynomial-time algorithm for integer decomposition is known.</p><h3>Brute-Force Factorization with the Sieve of Eratosthenes</h3><p>We will start with a naïve implementation of our factorization algorithm. We will start by listing all primes less than $n$ and check which prime in that list is a divisor. An improvement would be to only check primes less than or equal to the square root of <em>n</em> because the number is a product of two primes, so the smaller prime has to be less than or equal to its square root.</p><p>We will use the sieve of Eratosthenes to find primes in a specific range. This approach goes through all numbers starting from 2 to the end of the range and progressively removes all other that are multiples of it. We end up with a list of primes, all that is left is to check the first one that divides <em>n</em>.</p><pre>import math<br><br>def naive(n):<br>    sqrt_n = int(math.sqrt(n))<br><br>    isPrime = [True for i in range(0,sqrt_n+1)]<br>    primes = []<br><br>    for p in range(2, sqrt_n+1):<br>        if isPrime[p]:<br>            primes.append(p)<br>            for x in range(p**2, sqrt_n+1, p):<br>                isPrime[x] = False<br>    <br>    prime1 = 0<br>    for p in primes:<br>        if n%p == 0:<br>            prime1 = p<br>            break<br>    return prime1</pre><h4>Analysis</h4><p>The code above will factorize the integer <em>n</em> by building up the list of primes up to the square root of <em>n</em>, and checking whether any of these primes divide <em>n</em>. This algorithm has a log-linear every-case time complexity. Its proof is shown below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/399/1*iyktZxaqfkgizI4dSJT2oA.png" /><figcaption>Order notation of the brute-force algorithm</figcaption></figure><p>Its space complexity is in the order of square root of <em>n</em> (since we have to keep track of all numbers less than the square root of <em>n</em>).</p><h3>Pollard’s Rho</h3><p>This algorithm is implemented in a very simple manner but is based on a pseudorandom sequence. This sequence is generated by a polynomial function which is generally chosen as<em> g(x) = (x²+1) mod n</em>. The algorithm proceeds by progressively applying the polynomial function on a starting seed value (usually 2) and checking once two different random numbers are congruent to the same value modulo <em>n</em>. If so, the prime is simply the absolute value of the difference between the two numbers.</p><p>This approach can be thought of as a form of branch and bound where the state space tree are all pairs of numbers <em>(x,y)</em>, this pair is considered promising once its difference (in absolute value) isn’t co-prime with n anymore.</p><pre>import math<br><br>def g(x,n): return (x**2 + 1)%n<br><br>def rho(n):<br>    x = 2<br>    y = x<br>    d = 1<br>    while (d==1):<br>        x = g(x,n)<br>        y = g(g(y,n),n)<br>        d = math.gcd(abs(x-y), n)<br>    return d</pre><h4>Analysis</h4><p>The algorithm highly depends on the pseudorandom nature of the polynomial function modulo <em>n</em>. If this randomness is assumed, we base our analysis on the birthday paradox. This paradox is based on the counterintuitive fact that it only takes a minimum of 23 people to have a probability greater than 50% that two of them share the same birthday. This is due to the fact that any two people could share a birthday, so an added person’s birthday is compared to every other birthday: this combination of possible pairings grows exponentially with the size of the group.</p><p>Similarly, the Pollard’s Rho algorithm keeps track of two pseudorandom numbers. Its analysis depends on the implicit sequence <em>{ x mod p }</em> where p is a non-trivial factor of <em>n</em>. Assuming randomness, we can expect the two computed numbers to be different but the same modulo n to occur after at most the square root of the prime factor <em>p:</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/158/1*buFgsv14V9HV1iU-QFFxiQ.png" /></figure><p>The space complexity is <em>O(1)</em> since we only keep track of two numbers generated from a pseudorandom function and computing whether their promising (their difference isn’t co-prime with <em>n</em>).</p><h3>Implementation Analysis</h3><p>After implementing both algorithms in Python and running them on a test file containing semi-primes from 8 to 58 bits in size (with values from <em>143 </em>to <em>149470864377634489</em>), we measure their time and space complexity and obtain the following dataset description:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/668/1*pxSHhRwkYd4N3f7DQs4POw.png" /></figure><p>Note that to obtain the memory size accumulated by a python script, we use the <em>tracemalloc</em> module in Python as follows:</p><pre>import tracemalloc<br><br>tracemalloc.start() <br># Call the function to analyze<br>mem = tracemalloc.get_traced_memory()<br>tracemalloc.stop()</pre><p>The variable <em>mem</em> will contain a tuple of the current memory allocated for the script as well as the largest memory that the script used. Hence, we obtain <em>mem[1]</em> as the memory needed by the function to execute.</p><p>As we can see from the output result, the average time of the two algorithms are drastically different. For the brute force implementation, it takes around <em>22.5</em> seconds to factorize the integer while it only takes <em>0.008764</em> seconds using Pollard’s Rho. The time taken by the naïve implementation also ranges from <em>0.00003</em> to <em>394.77549</em> seconds as the size of the integer increased while this range is only from <em>0.00002</em> to <em>0.08404</em> seconds for all input.</p><p>The amount of memory used is also considerably different. The brute force implementation used between <em>336</em> to <em>4.1913 </em>x<em> 10⁹</em> bytes of memory with an average of <em>2.365</em> x <em>10⁸</em> bytes. This is considerably more memory intensive than the Pollard’s Rho algorithm which only used between <em>84 </em>to <em>504 </em>bytes of memory with an average of <em>223.38</em> bytes.</p><p>While the brute force implementation was able to factorize all inputs it was given (success rate of 100%), Pollard’s Rho wasn’t able to factorize 4 out of the 205 integers for a success rate of 98%. This could be resolved by repeating Pollard’s Rho with a different starting value for <em>x</em>. However, we didn’t rerun the algorithm to be authentic to its original prediction and to note its limitations due to its randomized nature.</p><p>The analysis we performed above was correct where we predicted that Pollard’s Rho would be more time efficient that the brute force. We also correctly determined that Pollard’s Rho space requirement is constant, independent of the input size, while the brute force implementation requires additional space as the size of the input increases to store the list of primes.</p><p>We can also plot the time needed to executed with respect to the size of the input N as you can see below (blue plot is for the naïve implementation and the red one is for Pollard’s Rho):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/604/1*LzLI_wal4oE9QXukPMB9qA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/585/1*TAma0KqI3CNuYP-bK9jHJQ.png" /><figcaption>Plotting execution time against the input (right) and its size (left)</figcaption></figure><p>This is exponential in the size of N (measured as the number of bits in the binary representation of <em>n</em>, i.e., <em>~ log(n) </em>), but log-linear with N.</p><h3>Conclusion</h3><p>In conclusion, we can see how two different approaches to solve the problem of integer factorization can result in different time and space complexities. While the brute force algorithm is deterministic and allows to compute a list of primes as it is solving the problem, it is very inefficient in terms of time and space requirements. On the other hand, Pollard’s Rho algorithm is very efficient in terms of time and space, but it is based on a pseudorandom sequence and doesn’t have an absolute guarantee on factorizing an integer. In fact, proving the time-complexity of Pollard’s Rho is still an open problem in mathematics and computer science.</p><p>While the problem of large integer factorization has been explored for decades, no efficient algorithm has been found on classical computers yet. This has led to cryptographic schemas to rely on the difficulty of integer factoring for the confidentiality of the encrypted data. However, efficient quantum algorithms for integer factorization has been proven to exist. And as quantum computers are evolving to be more reliable and faster, efficiently factoring an integer might become a thing of the past.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8ea545cd6077" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ML for Network Intrusion Detection — Part II: Machine Learning Training]]></title>
            <link>https://medium.com/@elirizk/ml-for-network-intrusion-detection-part-ii-machine-learning-training-32369e1f23e1?source=rss-48eed76153f1------2</link>
            <guid isPermaLink="false">https://medium.com/p/32369e1f23e1</guid>
            <category><![CDATA[decision-tree]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[neural-networks]]></category>
            <category><![CDATA[cybersecurity]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Eli Rizk]]></dc:creator>
            <pubDate>Mon, 18 Dec 2023 03:44:53 GMT</pubDate>
            <atom:updated>2024-05-20T18:19:20.561Z</atom:updated>
            <content:encoded><![CDATA[<h3>ML for Network Intrusion Detection — Part II: Machine Learning Training</h3><h4>If you haven’t yet, check part I of this blog post where we performed some data pre-processing, exploratory data analysis, and feature engineering (PCA) on the original dataset. This post is also available <a href="https://elirizk.me/projects/ml-nids-2/">here</a>.</h4><p>As a reminder, the dataset we’re using can be found here: <a href="https://www.kaggle.com/datasets/sampadab17/network-intrusion-detection">https://www.kaggle.com/datasets/sampadab17/network-intrusion-detection</a>. We are also training the machine learning models on the 10 principal components produced by our feature engineering approach, except for the DNN which will be using the original features.</p><p>We will start by defining a helpful function to print the error metrics of the ML models we will train.</p><pre>def print_error_metrics(y_test, y_pred):<br>    acc = metrics.accuracy_score(y_test, y_pred)<br>    prc = metrics.precision_score(y_test, y_pred)<br>    f1 = metrics.f1_score(y_test, y_pred)<br>    print(&#39;Accuracy: {:.5f}&#39;.format(acc))<br>    print(&#39;Precision: {:.5f}&#39;.format(prc))<br>    print(&#39;F1 Score: {:.5f}&#39;.format(f1))</pre><h3>Logistic Regression Models</h3><p>We will use an unregularized logistic regression model to fit on the training data.</p><pre>regressor = LogisticRegression(max_iter=100, penalty=&#39;none&#39;)<br>regressor.fit(X_train_PCA, y_train)<br>y_pred = regressor.predict(X_test_PCA)<br>print_error_metrics(y_test, y_pred)</pre><p>We obtain the following metrics on this model: Accuracy: 0.95773, Precision: 0.96615, and F1 Score: 0.95379. While promising, we regularize this version by using an elastic net regularization with an L1 ratio of 0.5. This will be helpful in avoiding overfitting which can make the model more generalizable to unseen data.</p><pre>regressor = LogisticRegression(max_iter=400, solver=&#39;saga&#39;, penalty=&#39;elasticnet&#39;, l1_ratio=0.5)<br>regressor.fit(X_train_PCA, y_train)<br>y_pred = regressor.predict(X_test_PCA)<br>print_error_metrics(y_test, y_pred)</pre><h3>Decision Trees</h3><pre>dtree = DecisionTreeClassifier(max_depth=None)<br>dtree.fit(X_train_PCA, y_train)<br>print(&quot;Decision tree maximum depth:&quot;, dtree.tree_.max_depth)<br>y_pred = dtree.predict(X_test_PCA)<br>print_error_metrics(y_test, y_pred)</pre><p>After training a decision tree classifier, we obtain the following metrics: Accuracy: 0.99147, Precision: 0.98974, and F1 Score: 0.99080.</p><p>While the results are promising, after looking into the decision tree produced, we realize that its maximum depth is 19. This implies that it might be overfitting on the training data: some decisions aren’t obtained until 19 separate splits are made on one of the 10 principal components. A zoomed out snapshot of the decision tree (as well as the code that produces it) is provided below. Note that the code changes the default color of the decision tree to paint the normal class in green and the anomalous class in red.</p><pre>from matplotlib.colors import to_rgb<br><br>features = [&#39;PCA_1&#39;, &#39;PCA_2&#39;, &#39;PCA_3&#39;, &#39;PCA_4&#39;, &#39;PCA_5&#39;, &#39;PCA_6&#39;, &#39;PCA_7&#39;, &#39;PCA_8&#39;, &#39;PCA_9&#39;, &#39;PCA_10&#39;]<br>fig = plt.figure(figsize=(100,90))<br>class_colors=[&#39;green&#39;, &#39;red&#39;]<br>artists = plot_tree(dtree, feature_names=features, class_names=[&#39;Normal&#39;, &#39;Anomaly&#39;], filled=True, rounded=True, fontsize=10)<br>for artist, impurity, value in zip(artists, dtree.tree_.impurity, dtree.tree_.value):<br>    r, g, b = to_rgb(class_colors[np.argmax(value)])<br>    f = impurity * 2<br>    artist.get_bbox_patch().set_facecolor((f + (1-f)*r, f + (1-f)*g, f + (1-f)*b))<br>    artist.get_bbox_patch().set_edgecolor(&#39;black&#39;)<br>fig.savefig(&#39;decision_tree_1.png&#39;)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uWu9bY8EGn1pyB6Kpo-NEw.png" /></figure><p>As you can see from the tree structure, this is an overly complex models considering it’s trained on 10 features. To regularize this model, we manually set the maximum depth of the tree to 5 in the <em>DecisionTreeClassifier </em>class parameter. As a result, we obtain the following metrics: Accuracy: 0.97658, Precision: 0.98300, and F1 Score: 0.97450. The decision tree produced is found below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UhX1f923KITwH98LYs3j_Q.png" /><figcaption>Decision tree model with maximum depth of 5</figcaption></figure><p>Note that the red leaves indicate a majority of malicious samples (so it will be predicted as malicious) and the green leaves have a majority of benign samples (predicted as benign). The more transparent a leaf is, the higher its entropy (the model will predict the majority class label but will be unsure about it). Even though we significantly reduced the tree’s depth from 19 to 5, its accuracy and precision barely dropped. This makes us more confident in recommending this version of the regularized model as opposed to the overfit one.</p><h3>Deep Neural Network</h3><p>We start by specifying the architecture of the model. The neural network passes the original 118 features to a hidden layer of 20 nodes which then passes them to another hidden layer of 10 nodes which finally sends them to an output node. Note that the ReLU activation function is used (to avoid the problem of vanishing gradients) except for the final layer which uses the sigmoid activation function to output probability values.</p><pre>class SimpleNN(torch.nn.Module):<br>    def __init__(self):<br>        super(SimpleNN, self).__init__()<br>        self.fc1 = torch.nn.Linear(118, 20)<br>        self.relu1 = torch.nn.ReLU()<br>        self.fc2 = torch.nn.Linear(20, 10)<br>        self.relu2 = torch.nn.ReLU()<br>        self.fc3 = torch.nn.Linear(10, 1)<br>        self.sigm = torch.nn.Sigmoid()<br><br>    def forward(self, x):<br>        x = self.fc1(x)<br>        x = self.relu1(x)<br>        x = self.fc2(x)<br>        x = self.relu2(x)<br>        x = self.fc3(x)<br>        x = self.sigm(x)<br>        return x</pre><p>We will use the binary cross entropy loss function as well as the Adam optimizer to train this model on the training data.</p><pre>model = SimpleNN()<br>criterion = torch.nn.BCELoss()<br>optimizer = torch.optim.Adam(model.parameters(), lr=0.001)<br>dataset = TensorDataset(torch.from_numpy(X_train_norm).type(torch.float), torch.from_numpy(y_train.to_numpy()).type(torch.float))<br>train_loader = DataLoader(dataset, batch_size=64, shuffle=True)<br>X_test_DNN = torch.from_numpy(X_test_norm).type(torch.float)<br>y_test_DNN = torch.from_numpy(y_test.to_numpy()).type(torch.float)<br><br>epochs = 20<br>loss_value = 0.0<br>train_loss = []<br>test_loss = []<br>for epoch in range(epochs):<br>    model.train()<br>    i = 0<br>    for _batch_idx, (features, labels) in enumerate(tqdm(train_loader)):<br>        i += 1<br>        optimizer.zero_grad()<br>        outputs = model(features).squeeze()<br>        loss = criterion(outputs, labels)<br>        loss.backward()<br>        optimizer.step()<br><br>        loss_value += loss.item()<br>    train_loss.append(loss_value/i)<br>    # Testing<br>    model.eval()<br>    with torch.inference_mode():<br>        test_pred = model(X_test_DNN).squeeze()<br>        test_loss_val = criterion(test_pred, y_test_DNN).item()<br>        test_loss.append(test_loss_val)<br>        print(f&#39;Epoch {epoch+1}/{epochs}, Training Loss: {loss_value/i:.10f}, Test Loss: {test_loss_val:.10f}&#39;)<br>    loss_value = 0.0<br>print(train_loss)</pre><p>After 20 epochs, we obtain the following train and test loss curves:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/565/1*cMbjGGV2tRganEWyN0u45w.png" /><figcaption>DNN training for 20 epochs</figcaption></figure><p>Considering that the testing loss fluctuates and increases slightly beyond the fifth epoch while the training loss is decreasing, we decide to employ early stopping to stop training the model at epoch 5 (by changing our <em>epoch </em>variable to 5 in the code above). This results in the following learning curve:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/565/1*X0dfP9KkamTLZpRlPFFUnw.png" /><figcaption>DNN model training with 5 epochs</figcaption></figure><p>To evaluate our model, we run the code below:</p><pre>model.eval()<br>with torch.inference_mode():<br>    test_pred = model(X_test_DNN).squeeze()<br>    test_loss = criterion(test_pred, y_test_DNN)<br>    y_pred = []<br>    for pred in test_pred:<br>        if pred&gt;0.5: y_pred.append(1)<br>        else: y_pred.append(0)<br>    print_error_metrics(y_test_DNN, y_pred)</pre><p>We obtain the following metrics: Accuracy: 0.99484, Precision: 0.99656, and F1 Score: 0.99442.</p><h3>Conclusion</h3><p>In conclusion, it is clear how data pre-processing and feature engineering can be of incredible help in the ML workflow. As for the specific ML models, we recommend the use of decision trees (regularized with a maximum depth of 5) for network intrusion detection as captured by the dataset we used. This model provides high accuracy for intrusion detection while being regularized against overfitting and being explainable in nature as captured by its decision nodes (as opposed to the unexplainable nature of the deep neural network model we developed).</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=32369e1f23e1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ML for Network Intrusion Detection — Part I: Feature Engineering]]></title>
            <link>https://medium.com/@elirizk/ml-for-network-intrusion-detection-part-i-feature-engineering-4672b45c3221?source=rss-48eed76153f1------2</link>
            <guid isPermaLink="false">https://medium.com/p/4672b45c3221</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[feature-engineering]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[cybersecurity]]></category>
            <category><![CDATA[pca-analysis]]></category>
            <dc:creator><![CDATA[Eli Rizk]]></dc:creator>
            <pubDate>Mon, 18 Dec 2023 02:23:10 GMT</pubDate>
            <atom:updated>2024-05-20T18:18:47.762Z</atom:updated>
            <content:encoded><![CDATA[<h3><strong>ML for Network Intrusion Detection — Part I: Feature Engineering</strong></h3><h4>In this blog post, we will explore the area of developing a machine learning model for network intrusion detection. This is a two-part blog series. The first part focuses on data pre-processing and feature engineering. The second part trains and evaluates different machine learning models. This post is also available <a href="https://elirizk.me/projects/ml-nids-1/">here</a>.</h4><p>Traditional intrusion detection systems have used the notion of an allow list or block list to only allow harmless operations or to detect know malicious activities, respectively. This requires a regularly updated configuration file listing the user operations to allow or block, e.g. known malicious IP addresses, outside worktime logins, etc. While this type of network intrusion detection system requires constant modification and a considerable initial investment to produce the original configuration, a novel approach to intrusion detection has been emerging: the use of machine learning models to automate the task of detecting potential intruders. As such, in this post, we will develop a machine learning model to recognize hidden patterns in anomalous network activity. To this end, we will perform some data pre-processing on the dataset we obtain, explore various machine learning models, and recommend the best model according to their performance and other metrics.</p><p>The dataset we will be using for the rest of our implementation can be found here: <a href="https://www.kaggle.com/datasets/sampadab17/network-intrusion-detection">https://www.kaggle.com/datasets/sampadab17/network-intrusion-detection</a>.</p><h3>Import libraries and set display configuration</h3><p>Before processing our dataset, we will have to import the necessary libraries to use their helpful commands.</p><pre>import torch<br>from torch.utils.data import DataLoader, TensorDataset<br>from torchviz import make_dot<br>import pandas as pd<br>import numpy as np<br>import matplotlib<br>import matplotlib.pyplot as plt<br>from mpl_toolkits.mplot3d import Axes3D<br>from sklearn.model_selection import train_test_split<br>from sklearn import metrics, preprocessing<br>from sklearn.decomposition import PCA<br>from sklearn.linear_model import LogisticRegression<br>from sklearn.tree import DecisionTreeClassifier, plot_tree<br>from sklearn.ensemble import RandomForestClassifier<br>from tqdm import tqdm<br>import seaborn as sns</pre><p>I also like setting the display configuration of the pandas module to render all columns, rows, or sequences (as opposed to setting a threshold on its maximum value. This can be done with the following code:</p><pre>pd.set_option(&#39;display.max_columns&#39;, None)<br>pd.set_option(&#39;display.max_rows&#39;, None)<br>pd.set_option(&#39;display.max_seq_item&#39;, None)</pre><h3>Exploratory Data Analysis</h3><p>We will start by examining the columns available in the dataset.</p><pre>df.columns<br>df = pd.read_csv(&#39;Train_data.csv&#39;)<br>df.columns</pre><p>We obtain the following list of features and class labels (note that the column <em>class</em> contains the label of the entry: Normal or Anomaly). We also note that the columns: <em>protocol_type</em>, <em>service </em>and <em>flag </em>are categorical while the rest are numerical. The total number of columns for this dataset is 43 columns.</p><pre>[&#39;duration&#39;, &#39;protocol_type&#39;, &#39;service&#39;, &#39;flag&#39;, &#39;src_bytes&#39;,<br>&#39;dst_bytes&#39;, &#39;land&#39;, &#39;wrong_fragment&#39;, &#39;urgent&#39;, &#39;hot&#39;,<br>&#39;num_failed_logins&#39;, &#39;logged_in&#39;, &#39;num_compromised&#39;, &#39;root_shell&#39;,<br>&#39;su_attempted&#39;, &#39;num_root&#39;, &#39;num_file_creations&#39;, &#39;num_shells&#39;,<br>&#39;num_access_files&#39;, &#39;num_outbound_cmds&#39;, &#39;is_host_login&#39;,<br>&#39;is_guest_login&#39;, &#39;count&#39;, &#39;srv_count&#39;, &#39;serror_rate&#39;,<br>&#39;srv_serror_rate&#39;, &#39;rerror_rate&#39;, &#39;srv_rerror_rate&#39;, &#39;same_srv_rate&#39;,<br>&#39;diff_srv_rate&#39;, &#39;srv_diff_host_rate&#39;, &#39;dst_host_count&#39;,<br>&#39;dst_host_srv_count&#39;, &#39;dst_host_same_srv_rate&#39;,<br>&#39;dst_host_diff_srv_rate&#39;, &#39;dst_host_same_src_port_rate&#39;,<br>&#39;dst_host_srv_diff_host_rate&#39;, &#39;dst_host_serror_rate&#39;,<br>&#39;dst_host_srv_serror_rate&#39;, &#39;dst_host_rerror_rate&#39;,<br>&#39;dst_host_srv_rerror_rate&#39;, &#39;class&#39;]</pre><p>To check for class imbalances, we plot the number of entries that are labelled as suspicious versus normal. This can safeguard against potential biases in the model being trained (adjusting for class imbalances).</p><pre>sns.countplot(x=df[&#39;class&#39;], palette=[&#39;green&#39;, &#39;red&#39;], hue=df[&#39;class&#39;])</pre><figure><img alt="Bar plot of the class labels (normal vs anomaly)" src="https://cdn-images-1.medium.com/max/589/1*eLZI1fXgtFwffn3PfBUq_g.png" /></figure><p>As you can see both classes are almost equally represented in the dataset. Hence, we don’t need to remove any instances of the majority class.</p><h4>Data Correlation</h4><p>We now follow by transforming the class label column to a binary dat (0 for normal and 1 for anomaly) and providing the Pearson correlation matrix of the dataset (excluding the categorical feature columns).</p><pre>categorical_columns = [&#39;protocol_type&#39;, &#39;service&#39;, &#39;flag&#39;]<br>df[&#39;class&#39;] = df[&#39;class&#39;].apply(lambda x: 0 if x==&quot;normal&quot; else 1)<br>plt.figure(figsize=(40,30))<br>sns.heatmap(df.drop(categorical_columns, axis=1).corr(), annot=True)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5B1JUX1HkSBKejLcR4AJ1w.png" /><figcaption>Correlation matrix with the 39 numerical features and the class label (last column/row)</figcaption></figure><p>As can be seen, most features aren’t highly correlated together (colored in light purple for a value of close to 0) while some feature pairs have either high positive (close to 1) or negative (close to -1) correlation (colored in white or dark purple respectively). For the class label column (last column and last index), only 9 of the 42 numerical features are highly correlated (with an absolute value greater than 0.5). However, this doesn’t mean that we can easily disregard the features with correlation close to 0. This is because the Pearson correlation value assumes a linear relationship between the two variable, which is an overly simplistic assumption in our case (it might be the case that the interplay of different features is highly correlated with the class label). For example, we decided to plot the variation of the same server rate variable against the destination host name server rate, producing the following scatter plot. Note that for the rest of this post, entries labelled as normal are visualized in green while those labeled anomalous are labeled in red.</p><pre>labels = df[&#39;class&#39;]<br>colors = [&#39;green&#39;,&#39;red&#39;]<br>y = df[&#39;dst_host_same_srv_rate&#39;]<br>x = df[&#39;same_srv_rate&#39;]<br>plt.scatter(x,y, c=labels, cmap=matplotlib.colors.ListedColormap(colors), alpha=0.5, s=40)<br>plt.xlabel(&quot;Same Server Rate&quot;)<br>plt.ylabel(&quot;Destination Host Name Server Rate&quot;)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/567/1*z7hctVR8WVVWinYXbFBWRw.png" /></figure><p>As can be seen, while some distinction can be drawn between suspicious and benign inputs, there is no clear cut separation that can yield a highly accurate model.</p><p>Before processing the data further, we perform one-hot encoding on the categorical features as well as split the dataset into training and testing data (80% split).</p><pre>df = df.join(pd.get_dummies(df.loc[:, categorical_columns]))<br>df = df.drop(categorical_columns, axis=1)<br>X_train, X_test, y_train, y_test = train_test_split(df.drop([&#39;class&#39;], axis=1), df[&#39;class&#39;], test_size=0.2, random_state=42)</pre><p>The shape of the training data becomes 20153 x 118. One-hot encoding significantly increased the number of features we were concerned with: we now have 118 features to train a model on. Considering that most features weren’t highly correlated to begin with, we decide to perform feature engineering on our dataset before training any model on it.</p><h3>Feature Engineering (PCA)</h3><p>We first begin by scaling our data to the standard normal distribution: mean of 0 and standard deviation of 1. Note that we fit the distribution on the training data and scale the test data accordingly, to prevent data leakage from the training to the testing set.</p><pre>scaler = preprocessing.StandardScaler()<br>scaler.fit(X_train)<br>X_train_norm = scaler.transform(X_train)<br>X_test_norm = scaler.transform(X_test)</pre><p>After doing so, we perform Principal Component Analysis (PCA) which reduces the dimensionality of our data. It is a form of unsupervised algorithm to produce a small set of uncorrelated variables called principal components. Its benefit is manifold including reducing the complexity of our models, alleviating the “curse of dimensionality”, and increasing the interpretability of the data. We decide to perform PCA to obtain 10 resulting components, i.e., new feature columns to use for our models.</p><pre>pca = PCA(n_components = 10)<br><br>X_train_PCA = pca.fit_transform(X_train_norm)<br>X_test_PCA = pca.transform(X_test_norm)<br><br>explained_variance = pca.explained_variance_ratio_<br>explained_variance</pre><p>The explained variance ratio of those 10 components are:</p><pre>[0.0834838 , 0.05251576, 0.03534561, 0.03201952, 0.02585129,<br>       0.02270666, 0.01908112, 0.01513291, 0.01363081, 0.01235519]</pre><p>Note that the components are sorted by this score. After plotting the Pearson correlation matrix for these new features as well as the class label, we obtain this new heat map:</p><pre>df_PCA = pd.DataFrame(X_train_PCA).corr()<br>df_PCA[&#39;class&#39;] = y_train<br>sns.heatmap(df_PCA.corr(), annot=True)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/527/1*IhbMju5G1znswwSAIvO1iQ.png" /><figcaption>Correlation heatmap of the 10 principal components with the class label</figcaption></figure><p>It is interesting to see that the correlation of the features with each other is -0.11 (which is close to -1/9) since it is part of the effect of PCA (producing uncorrelated features with equal correlation score). Concerning their correlation with the class label (last column or last index), we can note that most of these components are more strongly correlated with the label that their original 42 features. In fact, only one of them has a value of 0.071 while the others have values greater than or equal to 0.35 in absolute value. Compare that with the previous heatmap we calculated on the original features where most of them had a score close to 0.01 in absolute value.</p><p>In order to appreciate the power of PCA, we also perform a scatter plot using the first two PCA components as well as a 3D scatter plot using the first three components. This results in the following two figures:</p><pre>x = list(map(lambda x: x[0], X_train_PCA))<br>y = list(map(lambda x: x[1], X_train_PCA))<br>z = list(map(lambda x: x[2], X_train_PCA))<br>labels = y_train<br>colors = matplotlib.colors.ListedColormap([&#39;green&#39;,&#39;red&#39;])<br>plt.scatter(x,y, c=labels, cmap=colors, alpha=0.2, s=40)<br>plt.xlabel(&quot;First PCA Component&quot;)<br>plt.ylabel(&quot;Second PCA Component&quot;)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/565/1*9LPG-KzpK6q8MGZ_trPkuA.png" /><figcaption>Scatter plot of the 1st and 2nd principal components</figcaption></figure><pre>fig = plt.figure(figsize=(14,14))<br>ax = plt.axes(projection=&#39;3d&#39;)<br>labels = y_train<br><br># Creating plot<br>scatter = ax.scatter3D(x, y, z, c=labels, cmap=colors)<br>ax.set_zlim(-4,2)<br>ax.set_xlim(-4,3)<br>ax.set_xlabel(&quot;First PCA Component&quot;)<br>ax.set_ylabel(&quot;Second PCA Component&quot;)<br>ax.set_zlabel(&quot;Third PCA Component&quot;)<br>legend1 = ax.legend(*scatter.legend_elements(),<br>                    loc=&quot;upper right&quot;, title=&quot;Classes&quot;)<br>ax.add_artist(legend1)<br>plt.show()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0NT7yjKHIjaJedKcoVE4TA.png" /><figcaption>3D Scatter plot of the 1st, 2nd, and 3rd principal components</figcaption></figure><p>The power of feature dimensionality reduction through PCA allowed us to easily separate the class labels across the first two or three principal components. Surprisingly, the PCA algorithm was able to do that without looking at the class labels! The algorithm works only on the input dataset of features, not the labels. Feature engineering was able to significantly simplify the problem of anomaly detection by using the newly produced 10 principal components instead of the original 118 features.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4672b45c3221" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>