<?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"><channel><title><![CDATA[Ovawatch Security]]></title><description><![CDATA[Explore practical cybersecurity research from Ovawatch Security: web and API penetration testing, Active Directory attacks, bug bounty write-ups, and remediation.]]></description><link>https://ovawatch-security.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6ad53aca7dfda73761f76fa2/f85d5f85-d51b-4103-82f7-6f4932e979a0.png</url><title>Ovawatch Security</title><link>https://ovawatch-security.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 27 Oct 2026 04:01:33 GMT</lastBuildDate><atom:link href="https://ovawatch-security.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Case Study: Cross-Tenant Data Exfiltration via Backup Restore]]></title><description><![CDATA[Explore how broken object-level authorization in database backup restoration enables cross-tenant data exfiltration, with a PoC and remediation]]></description><link>https://ovawatch-security.hashnode.dev/case-study-cross-tenant-data-exfiltration-via-backup-restore</link><guid isPermaLink="true">https://ovawatch-security.hashnode.dev/case-study-cross-tenant-data-exfiltration-via-backup-restore</guid><category><![CDATA[appsec]]></category><category><![CDATA[#infosec]]></category><category><![CDATA[bugbounty]]></category><category><![CDATA[cyber]]></category><category><![CDATA[cybersecurity]]></category><category><![CDATA[IDOR]]></category><category><![CDATA[Application Security]]></category><dc:creator><![CDATA[Dimakatso Sebatane]]></dc:creator><pubDate>Sun, 18 Oct 2026 07:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ad53aca7dfda73761f76fa2/399fa2bf-bf24-44cf-a5db-862030ec8201.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Category:</strong> Web Application Security<br /><strong>Vulnerability:</strong> Broken Object Level Authorization (BOLA / IDOR)<br /><strong>CWE:</strong> <a href="https://cwe.mitre.org/data/definitions/639.html">CWE-639 — Authorization Bypass Through User-Controlled Key</a><br /><strong>OWASP:</strong> <a href="https://api-security.owasp.org/editions/2023/en/0xa1-broken-object-level-authorization/">API1:2023 — Broken Object Level Authorization</a></p>
<hr />
<h2>Introduction</h2>
<p>In a multi-tenant SaaS application, organizations may share the same infrastructure, but their data must remain isolated. A user authorized to access one organization's resources should not be able to read or copy another organization's data simply by supplying a different object identifier.</p>
<p>That principle seems straightforward. Yet authorization flaws often appear in workflows that <em>use</em> existing objects rather than merely display them. Database backup restoration is one example.</p>
<p>Consider a user who has read-only access to database backup metadata in one organization, while also administering a separate organization of their own. They cannot open the first organization's database records directly. But what happens if the application allows them to restore a backup into their own tenant using its identifier?</p>
<p>In this case study, we examine that scenario using <strong>Meridian</strong>, a fictional multi-tenant managed-database platform built as a controlled reproduction of a bug bounty finding. The lab demonstrates how a missing ownership check in a restore operation can turn limited metadata visibility into full access to another tenant's backed-up records.</p>
<p><strong>Lab:</strong> <a href="https://github.com/ovawatch-sec/bb-reproduction-labs/tree/main/Labs/bb-004-cross-tenant-database-exfil">Meridian — Cross-Tenant Backup Restore</a></p>
<h2>Understanding the Vulnerability</h2>
<p>Broken Object Level Authorization (BOLA), often discussed alongside Insecure Direct Object References (IDOR), occurs when an application uses an object identifier supplied by a client without verifying that the caller is authorized to perform the requested operation on that specific object.</p>
<p>The issue is not the existence of IDs. Applications need identifiers to reference projects, documents, backups, and other resources. The vulnerability appears when knowing or supplying a valid ID is mistakenly treated as sufficient permission.</p>
<p>Meridian supports multiple organizations, each with its own databases and backups. It also supports roles with different capabilities:</p>
<table>
<thead>
<tr>
<th>Role</th>
<th>Relevant permissions</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Reader</strong></td>
<td>View database and backup metadata within their organization, but not database records or restoration operations</td>
</tr>
<tr>
<td><strong>Admin</strong></td>
<td>Create and restore databases, manage records, and perform backup operations within their organization</td>
</tr>
</tbody></table>
<p>The attacker in this demonstration has two legitimate roles: <strong>Reader</strong> in the target organization, <em>Ovawatch</em>, and <strong>Admin</strong> in a separate organization, <em>Hacker Org</em>.</p>
<p>The security boundary should be clear: administrative control over Hacker Org must not grant access to Ovawatch's database backups. In Meridian's vulnerable restore workflow, that boundary is not enforced on the source backup reference.</p>
<h2>Proof of Concept</h2>
<h3>Step 1: A Reader Is Invited to the Target Workspace</h3>
<p>The owner of the <strong>Ovawatch</strong> workspace invites an external member with the <strong>Reader</strong> role.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad53aca7dfda73761f76fa2/e7a5a84e-110c-4adc-bcb9-b302b865ba7a.png" alt="Ovawatch workspace inviting an external member with read-only permissions" style="display:block;margin:0 auto" />

<p>This permission allows the member to inspect database and backup metadata. It does <strong>not</strong> allow them to read the underlying database records, create new databases, or restore backups inside Ovawatch.</p>
<p>The attacker also has an independent workspace named <strong>Hacker Org</strong>, where they hold the Admin role.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad53aca7dfda73761f76fa2/5809db10-f0f0-480a-8d4b-473528c096f0.png" alt="Attacker's separate Hacker Org workspace, where they hold administrative privileges" style="display:block;margin:0 auto" />

<p>This distinction matters: the attacker has not elevated their role within Ovawatch. They are exploiting an operation they are legitimately allowed to perform in <strong>their own</strong> organization, but with a resource that belongs to <strong>another</strong> organization.</p>
<h3>Step 2: Discovering a Backup Identifier</h3>
<p>While operating as a Reader in Ovawatch, the attacker views the available database backup metadata.</p>
<p>The interface reveals a backup ID, which uniquely references a snapshot of an Ovawatch database.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad53aca7dfda73761f76fa2/14811ef3-3c79-4e45-94a9-6c0eda8b2d33.png" alt="Read-only workspace member viewing a database backup identifier" style="display:block;margin:0 auto" />

<p>At this stage, the Reader's access is consistent with Meridian's intended permission model. Seeing the backup identifier does not, by itself, mean they are authorized to restore or read the backup's contents.</p>
<p>The critical question is whether <strong>every operation that accepts that backup ID</strong> independently checks which tenant owns it.</p>
<h3>Step 3: Restoring the Victim's Backup into a Different Tenant</h3>
<p>The attacker switches to Hacker Org, where they have permission to create databases.</p>
<p>Meridian's database provisioning workflow supports restoring a new database from an existing backup. The request includes a <code>sourceBackupId</code> identifying the snapshot to restore.</p>
<p>The attacker supplies the backup ID discovered in Ovawatch as the restoration source for a new database in Hacker Org.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad53aca7dfda73761f76fa2/80c3ad0a-5c7d-49df-9181-5abcd24d1d38.png" alt="Attacker supplying another tenant's backup ID when provisioning a database in Hacker Org" style="display:block;margin:0 auto" />

<p>This is where the authorization boundary fails. The application confirms that the backup exists, but does not confirm that it belongs to Hacker Org or is otherwise authorized as a restoration source for that organization.</p>
<p>As a result, the restore operation succeeds and creates a database inside the attacker's tenant using records from Ovawatch's backup.</p>
<h3>Step 4: Accessing the Restored Records</h3>
<p>After restoration, the newly created database belongs to Hacker Org. Because the attacker is an Admin there, they can read its records through ordinary application functionality.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad53aca7dfda73761f76fa2/48eb358b-5228-45fa-b152-e64505821bd3.png" alt="Attacker viewing records restored from the victim organization's database backup" style="display:block;margin:0 auto" />

<p>The reproduced behavior demonstrates that a user who could only inspect backup metadata in Ovawatch can now access the underlying record contents after restoring the snapshot into their own organization.</p>
<p><strong>The attack does not require administrator privileges in the victim tenant.</strong> It combines legitimate read-only metadata access in the victim tenant with legitimate restoration permissions in the attacker's tenant, exploiting a missing authorization check between them.</p>
<h2>Root Cause: The Restore Endpoint Checks Existence, Not Ownership</h2>
<p>Meridian exposes a database creation endpoint that can optionally restore from a backup:</p>
<pre><code class="language-http">POST /v1/orgs/{orgId}/databases
</code></pre>
<p>The request may include a <code>sourceBackupId</code>. In the vulnerable lab, the restore handler resolves that identifier using an unscoped lookup:</p>
<pre><code class="language-javascript">const backup = db.findBackupById(body.sourceBackupId);
</code></pre>
<p>This retrieves a matching backup from the platform's data store <strong>regardless of which organization owns it</strong>.</p>
<p>The application checks whether the backup exists, but the existence check is not an authorization check. It then uses the backup's snapshot records to provision a database under the requesting organization.</p>
<p>The same codebase correctly scopes other operations to the organization. For example, its direct backup-read path and project reference checks enforce tenant ownership. The inconsistent use of an unscoped lookup specifically in the restore path creates the vulnerability.</p>
<p>This is why the issue is best classified as <strong>Broken Object Level Authorization (BOLA / IDOR)</strong>, mapped to <strong>CWE-639 — Authorization Bypass Through User-Controlled Key</strong>. It is not primarily a role-escalation bug: no additional role is granted to the attacker in Ovawatch.</p>
<h2>Security Impact</h2>
<p>The demonstrated impact is <strong>cross-tenant disclosure of database records</strong> through unauthorized backup restoration.</p>
<p>The consequences in a production SaaS environment would depend on what the affected backups contain and which restore operations are available. Potential outcomes include:</p>
<ul>
<li><p>Exposure of confidential customer or business records stored in backed-up databases.</p>
</li>
<li><p>Unauthorized access to personal information, potentially triggering incident-response and regulatory obligations.</p>
</li>
<li><p>Extraction of larger historical datasets than a single live record-read operation might expose.</p>
</li>
<li><p>Loss of tenant-isolation guarantees, undermining customer trust in the service.</p>
</li>
<li><p>Financial, contractual, and legal consequences where sensitive data is actually exposed.</p>
</li>
</ul>
<p>The lab demonstrates unauthorized access to record content. It does <strong>not</strong> by itself establish that payment-card data, production PII, or a specific number of customers were exposed in a real environment.</p>
<h2>Remediation: Enforce Tenant Ownership at the Source</h2>
<h3>1. Scope Backup Lookups to the Requesting Organization</h3>
<p>The direct fix is to resolve the source backup <em>within the authorized tenant</em>, rather than looking it up globally.</p>
<p><strong>Vulnerable:</strong></p>
<pre><code class="language-javascript">const backup = db.findBackupById(body.sourceBackupId);
</code></pre>
<p><strong>Corrected:</strong></p>
<pre><code class="language-javascript">const backup = db.findBackupInOrg(body.sourceBackupId, orgId);

if (!backup) {
  return res.status(404).json({
    error: 'Backup not found.',
    code: 'NOT_FOUND'
  });
}
</code></pre>
<p>Where cross-organization restore is a legitimate business feature, it should require an explicit, auditable authorization mechanism rather than silently accepting any valid backup identifier.</p>
<h3>2. Enforce Object-Level Authorization on Every Reference</h3>
<p>Validate the caller's permission to use <strong>each</strong> referenced resource, including source backups, destination projects, database IDs, and any secondary objects involved in the workflow.</p>
<p>Having permission to create a database in one organization must not imply permission to use an arbitrary backup as its source.</p>
<h3>3. Centralize Tenant-Scoped Data Access</h3>
<p>Use repository or service methods that require tenant context, and avoid generic unscoped object lookups in business operations. Tenant-aware queries should form part of the application's authorization design, not depend on every developer remembering to add a separate check.</p>
<h3>4. Add Cross-Tenant Regression Tests</h3>
<p>Tests should cover legitimate same-tenant restoration, nonexistent backup IDs, and attempts to restore a backup from another tenant. Include accounts that have different roles across multiple organizations, because this is where cross-context authorization mistakes often emerge.</p>
<h3>5. Audit Sensitive Backup Operations</h3>
<p>Record the requesting identity, source backup, source tenant, destination tenant, and authorization outcome for restoration attempts. Alert on rejected cross-tenant references and unusual restore activity. Monitoring improves detection but cannot replace ownership checks.</p>
<h2>Conclusion</h2>
<p>This case study shows why authorization must be applied to <strong>how a resource is used</strong>, not simply to whether it can be viewed.</p>
<p>The attacker could not directly read Ovawatch's database records. Their Reader role allowed only backup metadata access. But Meridian's restore endpoint accepted the exposed backup ID without enforcing the backup's tenant ownership, allowing its contents to be copied into an attacker-controlled organization.</p>
<p>The failure was not in the identifier itself, nor in the existence of multiple workspaces. It was a missing authorization decision at the point where a backup crossed a tenant boundary.</p>
<p><strong>A valid object ID identifies a resource. It does not grant permission to use it.</strong></p>
<p>For developers, the lesson is to make tenant ownership checks mandatory wherever object references are resolved. For penetration testers, the lesson is to assess workflows that <em>consume or copy</em> protected resources — including backup restore, import, clone, export, and migration operations — in addition to conventional read and update endpoints.</p>
<hr />
<h2>Reproduce the Vulnerability</h2>
<p>The intentionally vulnerable Meridian lab includes setup instructions, separate organizations, role-based access, database backups, and the restore workflow used in this case study.</p>
<p><a href="https://github.com/ovawatch-sec/bb-reproduction-labs/tree/main/Labs/bb-004-cross-tenant-database-exfil"><strong>View Meridian Cross-Tenant Backup Restore Lab on GitHub</strong></a></p>
<p><em>Meridian is a fictional platform used for a controlled reproduction of a vulnerability pattern. This article does not identify or disclose the affected live bug bounty program.</em></p>
<p><strong>Ovawatch Security — Application Security Research &amp; Penetration Testing</strong></p>
]]></content:encoded></item><item><title><![CDATA[Case Study: Pre-Account Takeover via OAuth Misconfiguration]]></title><description><![CDATA[Explore how insecure OAuth account linking enables pre-account takeover. A technical case study covering the attack, root cause, impact, and fixes.]]></description><link>https://ovawatch-security.hashnode.dev/case-study-pre-account-takeover-via-oauth-misconfiguration</link><guid isPermaLink="true">https://ovawatch-security.hashnode.dev/case-study-pre-account-takeover-via-oauth-misconfiguration</guid><category><![CDATA[appsec]]></category><category><![CDATA[#infosec]]></category><category><![CDATA[cyber]]></category><category><![CDATA[s]]></category><category><![CDATA[bugbounty]]></category><category><![CDATA[misconfiguration]]></category><category><![CDATA[oauth]]></category><category><![CDATA[account takeover]]></category><category><![CDATA[hacking]]></category><category><![CDATA[ethicalhacking]]></category><dc:creator><![CDATA[Dimakatso Sebatane]]></dc:creator><pubDate>Sat, 10 Oct 2026 07:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ad53aca7dfda73761f76fa2/c06df67c-a5b0-4cb8-b9aa-4c0467ba295a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>Relevant CWE: CWE-287 — Improper Authentication - Generic</p>
<h2>Introduction</h2>
<p>Imagine signing up for a new platform using your Google account. You successfully authenticate, access your dashboard, and start using the application. Everything appears normal.</p>
<p>But what if someone else already had access to your account before you even registered?</p>
<p>This is the risk associated with <strong>Pre-Account Takeover</strong>, a vulnerability class in which an attacker prepares an account using a victim's identity before the legitimate user joins the platform.</p>
<p>Unlike traditional account takeover attacks, where an attacker compromises an existing account, pre-account takeover exploits weaknesses in the account creation and authentication process.</p>
<p>One particularly interesting scenario occurs when an application supports multiple authentication methods, such as email/password registration and third-party authentication through Google, GitHub, or Microsoft.</p>
<p>If these authentication methods are not linked securely, an attacker may be able to register an account using someone else's email address and retain access when the legitimate owner eventually signs in.</p>
<p>In this case study, we'll explore how this vulnerability can occur using <strong>WorkNest</strong>, a fictional work-management platform built to reproduce a bug bounty finding in a controlled lab environment.</p>
<p>The demonstration shows how an attacker can pre-register an account using a victim's email address, wait for the victim to authenticate through Google, and subsequently access the same workspace using the password established during the initial registration.</p>
<h2>Understanding the Vulnerability</h2>
<p>WorkNest supports two authentication methods:</p>
<ol>
<li><p>Traditional registration using an email address and password.</p>
</li>
<li><p>Federated authentication through Google OAuth.</p>
</li>
</ol>
<p>Both authentication methods are intended to provide access to the same user account when they genuinely belong to the same person.</p>
<p>However, the application makes two critical security mistakes.</p>
<p><strong>First, WorkNest allows users to register accounts without verifying ownership of the supplied email address.</strong></p>
<p>This means an attacker can create an account using another person's email address without requiring access to that person's mailbox.</p>
<p><strong>Second, WorkNest automatically links Google authentication to an existing account when the email addresses match.</strong></p>
<p>The application does not establish that the person who originally registered the email/password account is the same person now authenticating through Google.</p>
<p>More importantly, the original password remains valid after the Google identity is linked.</p>
<p>This creates a dangerous situation where two different individuals can authenticate to the same account through separate login methods.</p>
<p>The victim uses Google, while the attacker continues using the password they created before the victim registered.</p>
<p>The underlying problem is not Google OAuth itself. It is how WorkNest establishes account ownership and links authentication methods.</p>
<h2>Proof of Concept: Exploiting Insecure Account Linking</h2>
<p>For this demonstration, we'll use the WorkNest reproduction lab developed by Ovawatch Security.</p>
<p><strong>Lab repository:</strong> <a href="https://github.com/ovawatch-sec/bb-reproduction-labs/tree/main/Labs/bb-003-pre-account-takeover-worknest">WorkNest Pre-Account Takeover — GitHub</a></p>
<p>The lab reproduces the authentication and account-linking behavior necessary to demonstrate a pre-account takeover through a Classic–Federated Merge attack.</p>
<h3>Step 1: Pre-registering an Account Using the Victim's Email</h3>
<p>The attack begins before the legitimate victim has created an account on WorkNest.</p>
<p>The attacker navigates to the application's registration page and creates an account using the victim's email address.</p>
<p>For the demonstration, the attacker supplies an email address belonging to the controlled victim test account and chooses a password they know.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad53aca7dfda73761f76fa2/8c4f3574-f6f1-40c9-b09b-90b9d33ed015.png" alt="WorkNest registration page showing the attacker creating an account using the victim's email address" style="display:block;margin:0 auto" />

<p>Under a secure registration process, the application should require proof that the registering user controls the supplied email address before granting the account verified status or access to sensitive functionality.</p>
<p>However, WorkNest does not enforce this verification requirement.</p>
<p>The account is created successfully, and the attacker can authenticate using the victim's email address and the attacker-chosen password.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad53aca7dfda73761f76fa2/88e5ee63-9890-4e36-81c4-e598268d13f6.png" alt="Attacker successfully authenticated to WorkNest using the victim's email address" style="display:block;margin:0 auto" />

<p>At this stage, the attacker has effectively reserved an account under an email address they do not own.</p>
<p>The important detail is that <strong>the attacker does not need access to the victim's mailbox or Google credentials</strong>.</p>
<p>They only need the ability to register an account using the victim's email address.</p>
<p>The attacker can now sign out and wait for the legitimate owner to register.</p>
<h3>Step 2: The Legitimate Victim Signs In Using Google</h3>
<p>Later, the legitimate owner visits WorkNest for the first time.</p>
<p>Instead of manually creating an account using an email address and password, the victim chooses the more convenient <strong>Continue with Google</strong> option.</p>
<p>This is a common authentication workflow used across modern SaaS applications.</p>
<p>The victim authenticates with their legitimate Google account, using the same email address that the attacker previously registered.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad53aca7dfda73761f76fa2/ef114737-60d5-4d22-b605-5604d91647c7.png" alt="Legitimate victim selecting Google authentication on WorkNest" style="display:block;margin:0 auto" />

<p>After Google authentication succeeds, WorkNest receives the victim's Google profile information.</p>
<p>The application then searches its local database for an account associated with the returned email address.</p>
<p>Because the attacker has already registered that email, WorkNest finds the existing account.</p>
<p>Instead of requiring additional verification or explicit account linking, the application automatically associates the Google identity with the pre-existing account.</p>
<p>From the victim's perspective, nothing appears unusual.</p>
<p>They successfully authenticate, reach their workspace, and can start using the platform.</p>
<p>However, behind the scenes, <strong>the Google identity has been attached to an account originally created by the attacker</strong>.</p>
<h3>Step 3: The Authentication Methods Are Linked Insecurely</h3>
<p>This is the critical point in the attack.</p>
<p>WorkNest now recognizes two authentication methods for the same account:</p>
<ul>
<li><p><strong>Google authentication:</strong> Controlled by the legitimate victim.</p>
</li>
<li><p><strong>Email/password authentication:</strong> Still accessible using the password chosen by the attacker.</p>
</li>
</ul>
<p>The application has treated matching email addresses as sufficient evidence that the authentication methods belong to the same person.</p>
<p>However, the original account was never verified as belonging to the legitimate email owner.</p>
<p>To make matters worse, WorkNest does not invalidate the original local password when the victim successfully authenticates through Google.</p>
<p>The attacker therefore retains a valid authentication method.</p>
<p>This creates a shared-account situation in which the victim unknowingly uses an account that the attacker can still access.</p>
<h3>Step 4: The Attacker Regains Access to the Victim's Workspace</h3>
<p>After authenticating through Google, the legitimate victim begins using WorkNest normally.</p>
<p>For this demonstration, the victim creates a new project within their workspace.</p>
<p>The attacker then returns to WorkNest and signs in using the original email address and password established during Step 1.</p>
<p>The login succeeds.</p>
<p>Because both authentication methods are linked to the same internal account, the attacker is redirected to the same workspace used by the victim.</p>
<p>The attacker can now view the project created after the victim authenticated through Google.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad53aca7dfda73761f76fa2/5d4a37c1-a98b-4e0e-8b37-c855555ae492.png" alt="Attacker accessing the project created by the legitimate victim" style="display:block;margin:0 auto" />

<p>This confirms the security impact of the vulnerability.</p>
<p><strong>The attacker has successfully retained access to the victim's account without compromising their Google credentials or obtaining their password.</strong></p>
<p>The victim may remain completely unaware that someone else can access the account through a separate authentication method.</p>
<p>In this reproduction, the attacker can access workspace data created by the victim. Depending on the functionality of a real application, similar vulnerabilities could expose additional sensitive resources.</p>
<h2>Root Cause: Insecure Account Linking</h2>
<p>To understand why this attack works, we need to examine how WorkNest handles authentication through Google.</p>
<p>The vulnerable logic is implemented in the application's <code>src/auth.js</code> file.</p>
<p>When WorkNest receives a Google profile, it extracts the user's email address and searches for an existing local account.</p>
<p>The relevant behavior can be simplified as follows:</p>
<pre><code class="language-javascript">const email = db.normalizeEmail(
  profile.emails[0].value
);

let user = db.findUserByEmail(email);

if (user) {
  user = db.updateUser(user.id, (current) =&gt; ({
    ...current,
    emailVerified: true,
    providerLinks: {
      ...(current.providerLinks || {}),
      google: profile.id
    }
  }));
}
</code></pre>
<p>The critical operation is:</p>
<pre><code class="language-javascript">let user = db.findUserByEmail(email);
</code></pre>
<p>If an account exists with the same email address, the application treats it as the account that should receive the Google authentication method.</p>
<p>It then attaches the Google identity to that account.</p>
<p>The existing local password is not removed or invalidated.</p>
<p>Additionally, the implementation marks the local account's email as verified without explicitly checking an email-verification indicator from the identity provider.</p>
<p><strong>The security failure is the assumption that matching email addresses establish common account ownership.</strong></p>
<p>A valid Google authentication establishes control over a Google identity. It does not establish that the person who previously registered an unverified local account was the same individual.</p>
<p>WorkNest fails to enforce that distinction.</p>
<h3>Why This Is a Classic–Federated Merge Attack</h3>
<p>This scenario is known as a <strong>Classic–Federated Merge Attack</strong>, a form of account pre-hijacking.</p>
<p>It involves two different registration methods:</p>
<p><strong>Classic registration:</strong> A user creates an account directly using an email address and password.</p>
<p><strong>Federated registration:</strong> A user authenticates through an external identity provider, such as Google.</p>
<p>When the application automatically merges accounts across these authentication methods without establishing ownership of both identities, it may allow an attacker-controlled account to become associated with a legitimate user's identity.</p>
<p>The same attack pattern has been examined in published research on account pre-hijacking.</p>
<p>For additional technical background, see Microsoft's research on <a href="https://www.microsoft.com/en-us/msrc/blog/2022/05/pre-hijacking-attacks/">Pre-Hijacked Accounts</a>.</p>
<h2>Security Impact</h2>
<p>Pre-account takeover is particularly concerning because the malicious activity begins before the victim has even registered.</p>
<p>Unlike traditional attacks that require stealing credentials or hijacking an active session, this vulnerability allows the attacker to establish access in advance.</p>
<p>The demonstrated impact is unauthorized access to workspace data.</p>
<p>Depending on the affected application's functionality and permissions, a similar weakness could result in:</p>
<ul>
<li><p><strong>Unauthorized account access:</strong> An attacker retains a valid login method after the legitimate owner registers.</p>
</li>
<li><p><strong>Sensitive information disclosure:</strong> Projects, documents, customer records, and other private information may become accessible.</p>
</li>
<li><p><strong>Data manipulation:</strong> If the account has write permissions, the attacker may be able to modify or delete resources belonging to the victim.</p>
</li>
<li><p><strong>Business compromise:</strong> In collaborative SaaS environments, unauthorized access to shared workspaces could expose confidential company information.</p>
</li>
<li><p><strong>Persistent unauthorized access:</strong> The attacker may continue authenticating until their credentials are revoked or the account-linking issue is corrected.</p>
</li>
</ul>
<p>One of the most dangerous aspects is the lack of an obvious warning to the legitimate user.</p>
<p>From the victim's perspective, registration and authentication work as expected. They may have no reason to suspect the account was created by someone else.</p>
<p>The actual severity depends on the data and operations accessible through the compromised account, as well as additional controls such as multifactor authentication and session management.</p>
<h2>Remediation: Securing Registration and OAuth Account Linking</h2>
<p>Preventing this vulnerability requires enforcing clear ownership boundaries between local accounts and third-party identities.</p>
<h3>1. Verify Email Ownership During Registration</h3>
<p>Applications should require users to verify their email addresses before treating their accounts as verified identities.</p>
<p>Unverified accounts should not be eligible for automatic linking to external authentication providers.</p>
<p>However, email verification alone is not a complete solution.</p>
<p>An attacker may create an unverified account and wait for a legitimate user to verify ownership through another authentication method.</p>
<p>The application must also ensure that credentials established before ownership verification cannot remain accessible to an unauthorized party.</p>
<h3>2. Do Not Automatically Link Accounts Based Only on Email</h3>
<p>Matching email addresses should not be treated as sufficient proof that two authentication methods belong to the same person.</p>
<p>If a user authenticates through Google and an existing local account has the same email address, the application should require a secure account-linking or recovery process.</p>
<p>For example, a legitimate user who controls both accounts may authenticate using the existing local credentials and explicitly authorize the connection.</p>
<p>Where the existing local account is unverified or potentially attacker-created, the application should use a verified recovery process that does not rely on the untrusted password.</p>
<h3>3. Invalidate Untrusted Credentials and Sessions</h3>
<p>If a legitimate user proves ownership of an email address previously associated with an unverified account, the application must not automatically preserve credentials established before that verification.</p>
<p>A secure recovery process should invalidate potentially attacker-controlled passwords and sessions before allowing the account to become trusted.</p>
<p>This prevents a previously established authentication method from remaining usable after the rightful owner takes control.</p>
<h3>4. Use Stable Provider Identifiers</h3>
<p>Federated identities should be associated with stable identifiers issued by the identity provider rather than relying exclusively on email addresses.</p>
<p>For example, a Google identity can be linked using the provider's unique subject identifier.</p>
<p>Applications should also validate relevant provider identity assertions, including whether the email address has been verified.</p>
<p>However, using a stable provider identifier does not eliminate the need for a secure process when attaching that identity to an existing local account.</p>
<h3>5. Notify Users When Authentication Methods Change</h3>
<p>Users should receive security notifications when new authentication methods are added, passwords are created or changed, or external identity providers are linked.</p>
<p>Applications should also provide account settings that allow users to review and remove linked authentication methods.</p>
<p>These controls improve visibility and can help detect unauthorized changes.</p>
<h2>Conclusion</h2>
<p>This case study demonstrates how a weakness in account creation and authentication linking can result in account compromise before the legitimate user even registers.</p>
<p>By creating an account using the victim's email address, the attacker established an authentication method that remained valid after the victim signed in through Google.</p>
<p>Because WorkNest automatically linked both authentication methods without securely establishing account ownership, the attacker was able to access the same workspace as the victim.</p>
<p><strong>The fundamental mistake was trusting that two authentication methods belonged to the same person simply because they shared an email address.</strong></p>
<p>For developers, the lesson is that supporting multiple authentication providers requires more than validating credentials. Applications must also securely manage account ownership, identity linking, and the lifecycle of existing authentication methods.</p>
<p>For penetration testers and security researchers, this highlights why authentication assessments should examine the interaction between different registration and login flows, rather than evaluating each method independently.</p>
<p>A login flow can be secure in isolation while the logic connecting it to another authentication method creates an account takeover vulnerability.</p>
<p>And sometimes, an attacker doesn't need to steal an account.</p>
<p><strong>They only need to create it first.</strong></p>
<hr />
<h2>Reproduce the Vulnerability</h2>
<p>Explore the intentionally vulnerable WorkNest application and reproduce the authentication flow in a controlled environment.</p>
<p><a href="https://github.com/ovawatch-sec/bb-reproduction-labs/tree/main/Labs/bb-003-pre-account-takeover-worknest"><strong>View WorkNest Pre-Account Takeover Lab on GitHub</strong></a></p>
<p>The lab includes the vulnerable implementation, setup instructions, and reproduction steps.</p>
<p><em>This case study is based on a controlled lab reproduction inspired by a bug bounty finding. WorkNest is a fictional application, and the demonstration uses controlled test accounts.</em></p>
<p><strong>Ovawatch Security — Application Security Research &amp; Penetration Testing</strong></p>
]]></content:encoded></item><item><title><![CDATA[Case Study:  From HTML Injection to Credential Theft ]]></title><description><![CDATA[Explore how HTML Injection can lead to credential theft through a reproduced bug bounty case study covering exploitation, impact and remediation]]></description><link>https://ovawatch-security.hashnode.dev/case-study-from-html-injection-to-credential-theft</link><guid isPermaLink="true">https://ovawatch-security.hashnode.dev/case-study-from-html-injection-to-credential-theft</guid><category><![CDATA[ Web Application Security]]></category><category><![CDATA[appsec]]></category><category><![CDATA[#infosec]]></category><category><![CDATA[bugbounty]]></category><category><![CDATA[hacking]]></category><category><![CDATA[cybersecurity]]></category><category><![CDATA[XSS]]></category><dc:creator><![CDATA[Dimakatso Sebatane]]></dc:creator><pubDate>Sat, 03 Oct 2026 07:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ad53aca7dfda73761f76fa2/5802129e-7519-41e5-bb15-ea0558e9b688.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>Relevant CWE: CWE-79 — Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')</p>
<h2>Introduction</h2>
<p>When we think about vulnerabilities that lead to account compromise, SQL Injection, Cross-Site Scripting (XSS), and authentication bypasses often come to mind. HTML Injection, however, is frequently overlooked despite its potential to facilitate convincing phishing attacks.</p>
<p>Imagine receiving a link to a login page from a platform you regularly use. You inspect the URL, recognize the domain, and everything appears legitimate. What you don't realize is that the content displayed on the page has been manipulated by an attacker.</p>
<p>This is where HTML Injection becomes dangerous.</p>
<p><strong>HTML Injection occurs when an application renders untrusted input as HTML markup instead of treating it as plain text.</strong> Depending on where that input is rendered and which browser security controls are in place, an attacker may be able to modify visible content, introduce fraudulent forms, or create an entirely deceptive interface.</p>
<p>Unlike traditional phishing attacks that rely on lookalike domains, HTML Injection can allow malicious content to appear directly on a legitimate website.</p>
<p>In this case study, we'll examine how a seemingly harmless login error message can become an entry point for credential phishing.</p>
<p>The demonstration uses <strong>CloudDesk</strong>, an intentionally vulnerable web application created to reproduce a bug bounty vulnerability in a controlled lab environment.</p>
<h2>Understanding the Vulnerability</h2>
<p>CloudDesk is a fictional SaaS platform that allows users to authenticate and access their workspace.</p>
<p>When a user enters incorrect login credentials, the application redirects them to the sign-in page and displays an error message.</p>
<p>The message is supplied through the <code>error</code> query parameter:</p>
<pre><code class="language-text">https://clouddesk.example/signin?error=We couldn't sign you in. Check your email and password, and try again.
</code></pre>
<p>Under normal circumstances, this behavior seems harmless. The application reads the parameter and displays the error to the user.</p>
<p>The problem lies in <strong>how the application handles that value</strong>.</p>
<p>Instead of rendering the parameter as plain text, CloudDesk inserts it into the page as HTML.</p>
<p>For example, consider the following input:</p>
<pre><code class="language-html">&lt;div&gt;
  &lt;h1&gt;Custom Message&lt;/h1&gt;
&lt;/div&gt;
</code></pre>
<p>If the application safely handles the input, the HTML tags should appear as literal text.</p>
<p>However, if the browser interprets the supplied value as HTML, the user will see a large heading displaying <strong>Custom Message</strong>.</p>
<p>This indicates that attacker-controlled content is being treated as part of the application's HTML structure.</p>
<p>Now consider the implications of extending that capability beyond a simple heading. What if an attacker could inject styling, introduce new input fields, or construct a fake authentication interface?</p>
<p>The resulting page would still be served from the legitimate application's domain, but the content presented to the user could be attacker-controlled.</p>
<h2>Proof of Concept: Exploiting the Login Error Parameter</h2>
<p>To demonstrate the vulnerability, we'll use the CloudDesk reproduction lab developed by Ovawatch Security.</p>
<p><strong>Lab repository:</strong> <a href="https://github.com/ovawatch-sec/bb-reproduction-labs/tree/main/Labs/bb-002-html-injection-on-err-query-param-lab">CloudDesk HTML Injection Lab — GitHub</a></p>
<p>The lab recreates an unsafe login-message rendering mechanism and allows us to observe how untrusted input can progress from basic HTML manipulation to more serious security consequences.</p>
<h3>Step 1: Identifying the Injection Point</h3>
<p>We begin by examining the application's normal authentication behavior.</p>
<p>When invalid credentials are submitted, CloudDesk redirects the user to <code>/signin</code> with an error message supplied through the URL.</p>
<pre><code class="language-text">/signin?error=We couldn't sign you in. Check your email and password, and try again.
</code></pre>
<p>The application displays the parameter's value in the login error message area.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad53aca7dfda73761f76fa2/b7b58a67-bc95-447e-a4e3-9631d3d46165.png" alt="CloudDesk login page displaying an authentication error" style="display:block;margin:0 auto" />

<p>At this stage, the important observation is that the error message can be influenced through a query parameter.</p>
<p>Since URL parameters can be modified by anyone constructing a link, the next step is determining whether the application treats that value as text or HTML.</p>
<h3>Step 2: Confirming HTML Injection</h3>
<p>To test whether HTML markup is interpreted, we replace the error message with a simple HTML element.</p>
<p><strong>Test payload:</strong></p>
<pre><code class="language-html">&lt;u&gt;XSS&lt;/u&gt;
</code></pre>
<p>The resulting parameter becomes:</p>
<pre><code class="language-text">/signin?error=&lt;u&gt;XSS&lt;/u&gt;
</code></pre>
<p><em>The payload is shown in its decoded form for readability. Special characters should be URL-encoded when constructing the actual request.</em></p>
<p>The <code>&lt;u&gt;</code> element instructs the browser to underline its contents.</p>
<p>If CloudDesk safely escaped the input, the page would display the complete string, including the HTML tags.</p>
<p>Instead, the application renders the word <strong>XSS</strong> with an underline.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad53aca7dfda73761f76fa2/e38f381c-138f-4c52-8834-a6c7cc8241a8.png" alt="HTML injection confirmed through rendered underline formatting" style="display:block;margin:0 auto" />

<p>This confirms that the application is interpreting attacker-supplied input as HTML.</p>
<p><strong>Important distinction:</strong> Although the test string contains the word XSS, this particular payload only demonstrates HTML Injection. It does not independently prove that JavaScript execution is possible.</p>
<p>Nevertheless, we have identified the first important weakness: untrusted input can influence the page's markup.</p>
<h3>Step 3: Manipulating the Page with HTML and CSS</h3>
<p>Now that we know HTML elements are being rendered, we can investigate whether the application also accepts inline styling.</p>
<p>This matters because CSS allows an attacker to control the visual appearance of injected elements.</p>
<p>We supply a styled HTML element:</p>
<pre><code class="language-html">&lt;strong style="color:#d425c6"&gt;
  Injected HTML proof: this message was rendered as markup.
&lt;/strong&gt;
</code></pre>
<p>Instead of displaying the original error message, CloudDesk renders the injected content with the specified color.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad53aca7dfda73761f76fa2/6b58bfad-3dba-4201-935a-de78f5aa8b31.png" alt="Injected HTML rendered with attacker-controlled text styling" style="display:block;margin:0 auto" />

<p>This demonstrates that we are not limited to changing the message text. We can also influence its presentation.</p>
<p>With sufficient control over HTML and styling, attackers may be able to introduce convincing warnings, impersonate application notifications, or display interfaces that encourage users to perform sensitive actions.</p>
<p>At this point, the vulnerability is no longer just about displaying arbitrary text. It becomes a mechanism for manipulating what users see and trust.</p>
<h3>Step 4: Replacing the Login Interface</h3>
<p>This is where the attack becomes significantly more concerning.</p>
<p>Instead of injecting a heading or a styled message, consider an attacker who creates a complete authentication interface designed to resemble CloudDesk's legitimate sign-in page.</p>
<p>The injected content could include a familiar logo, email and password fields, and a message claiming that the user must reauthenticate to continue accessing their workspace.</p>
<p>In our reproduced attack scenario, the legitimate interface is visually replaced with an attacker-controlled login page.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad53aca7dfda73761f76fa2/79c6496b-2387-4424-af19-fcd838d4b6cc.png" alt="Attacker-controlled login interface displayed on the trusted application" style="display:block;margin:0 auto" />

<p>From the victim's perspective, the page may appear legitimate for several reasons:</p>
<ul>
<li><p>The browser address bar still displays the trusted application's domain.</p>
</li>
<li><p>The page resembles the platform's original login interface.</p>
</li>
<li><p>The authentication request appears consistent with a normal session-expiration or verification workflow.</p>
</li>
</ul>
<p>This creates an opportunity for credential phishing.</p>
<p>Rather than redirecting the victim to an obvious third-party phishing website, the attacker abuses the legitimate application itself to deliver the deceptive content.</p>
<h3>Step 5: Demonstrating the Credential-Theft Impact</h3>
<p>The final stage illustrates how an attacker-controlled login interface could be used to obtain credentials.</p>
<p>In the attack scenario, a victim who trusts the displayed form enters their email address and password.</p>
<p>If the injected form is configured to submit those values to an attacker-controlled destination, the credentials can be captured.</p>
<p>The attacker may then return the victim to the original sign-in page, making the interaction appear to be a temporary authentication error rather than a phishing attempt.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad53aca7dfda73761f76fa2/ff34667b-3007-4670-81bb-51f80ec5d9db.png" alt="Proof-of-concept demonstration showing credential capture" style="display:block;margin:0 auto" />

<p>In a real attack, successfully obtaining valid credentials could allow an attacker to attempt unauthorized access to the victim's account, potentially resulting in account takeover.</p>
<p>However, credential theft does not automatically guarantee account compromise. Controls such as phishing-resistant multifactor authentication can significantly reduce that risk.</p>
<p><strong>Lab clarification:</strong> The publicly available CloudDesk GitHub demonstration uses a non-exfiltrating page-replacement payload. It does not collect or transmit credentials. The credential-capture scenario illustrates the potential impact of the underlying vulnerability and should be demonstrated only with controlled test credentials.</p>
<h2>Root Cause: Why Was This Possible?</h2>
<p>The underlying issue is improper handling of untrusted input before rendering it in the browser.</p>
<p>In the CloudDesk lab, client-side JavaScript retrieves the error message from the URL.</p>
<p>The vulnerable behavior can be simplified to:</p>
<pre><code class="language-javascript">const params = new URLSearchParams(window.location.search);
const error = params.get("error");

messageBox.innerHTML = error;
</code></pre>
<p>The problem is the use of <code>innerHTML</code> with untrusted input.</p>
<p><code>innerHTML</code> instructs the browser to parse the supplied string as HTML markup rather than render it as plain text.</p>
<p>Consequently, the input can introduce additional HTML elements into the page.</p>
<p>The published CloudDesk lab also explicitly recreates injected <code>&lt;script&gt;</code> elements after inserting the HTML, enabling script execution. This additional behavior is significant because scripts added through ordinary <code>innerHTML</code> assignment do not automatically execute.</p>
<p>This creates two related security concerns:</p>
<p><strong>HTML Injection:</strong> An attacker can insert markup that alters the visible page or introduces deceptive interface elements.</p>
<p><strong>Cross-Site Scripting (XSS):</strong> When injected content can execute JavaScript, the attacker can potentially perform actions in the security context of the affected application.</p>
<p>In this case, the intentionally vulnerable rendering implementation demonstrates a path from reflected HTML Injection to script execution.</p>
<h2>Security Impact</h2>
<p>The severity of HTML Injection depends on the application's context, the rendering behavior, and the security controls protecting the page.</p>
<p>When the vulnerability affects a login page, the potential impact is particularly concerning because users are already expecting to interact with authentication forms.</p>
<p>A successful attack could result in:</p>
<ul>
<li><p><strong>Credential phishing:</strong> Attackers can create deceptive authentication forms within a legitimate website.</p>
</li>
<li><p><strong>Account takeover:</strong> Stolen credentials may enable unauthorized access when additional authentication controls are insufficient.</p>
</li>
<li><p><strong>Interface manipulation:</strong> Attackers can introduce false security warnings, misleading instructions, or fraudulent verification prompts.</p>
</li>
<li><p><strong>Reputation damage:</strong> Users may lose confidence in a platform whose legitimate pages are being abused for phishing.</p>
</li>
<li><p><strong>Financial and operational losses:</strong> Compromised accounts may expose sensitive information or enable fraudulent activity, depending on the application's functionality.</p>
</li>
</ul>
<p>An additional concern is scalability.</p>
<p>When an injection can be delivered through a crafted URL, an attacker may be able to distribute the malicious link through emails, messages, or other phishing channels without needing access to individual victim accounts beforehand.</p>
<p>The trusted domain becomes part of the attack's credibility.</p>
<h2>Remediation: Preventing HTML Injection</h2>
<p>The primary defense is ensuring that untrusted input is never interpreted as executable or structural HTML unless absolutely necessary.</p>
<h3>1. Render user-controlled values as text</h3>
<p>If an error message does not require HTML formatting, use <code>textContent</code> instead of <code>innerHTML</code>.</p>
<p><strong>Vulnerable implementation:</strong></p>
<pre><code class="language-javascript">messageBox.innerHTML = error;
</code></pre>
<p><strong>Safer implementation:</strong></p>
<pre><code class="language-javascript">messageBox.textContent = error;
</code></pre>
<p>This ensures that HTML tags are displayed as text rather than interpreted as elements.</p>
<h3>2. Avoid exposing arbitrary error messages through URL parameters</h3>
<p>Instead of accepting a complete error message from the URL, applications can use predefined error identifiers.</p>
<p>For example:</p>
<pre><code class="language-text">/signin?error=invalid_credentials
</code></pre>
<p>The client can then map the identifier to an approved message.</p>
<pre><code class="language-javascript">const messages = {
  invalid_credentials:
    "Invalid email or password.",
  session_expired:
    "Your session has expired."
};

const code = new URLSearchParams(location.search).get("error");

messageBox.textContent =
  messages[code] ?? "Unable to sign in.";
</code></pre>
<p>This reduces the application's reliance on arbitrary URL-supplied content.</p>
<h3>3. Sanitize HTML when rich formatting is required</h3>
<p>If an application genuinely needs to render user-supplied HTML, use a well-maintained HTML sanitizer with a restrictive allowlist.</p>
<p>Untrusted content should not be able to introduce dangerous elements, event handlers, or unauthorized destinations.</p>
<h3>4. Implement a restrictive Content Security Policy</h3>
<p>A properly configured Content Security Policy (CSP) can provide an additional layer of protection.</p>
<p>For example, appropriate <code>script-src</code> restrictions can limit unauthorized script execution, while <code>form-action 'self'</code> can restrict where HTML forms submit their data.</p>
<p>CSP is a defense-in-depth control, not a replacement for correct output handling and contextual sanitization.</p>
<h3>5. Include HTML Injection in security assessments</h3>
<p>During web application penetration testing, attention should be paid to locations where user-controlled values influence the rendered interface, particularly:</p>
<ul>
<li><p>Login and authentication error messages</p>
</li>
<li><p>Search result pages</p>
</li>
<li><p>Notification banners</p>
</li>
<li><p>User profile information</p>
</li>
<li><p>Redirect and status messages</p>
</li>
</ul>
<p>These areas may appear low-risk but can become valuable attack surfaces when untrusted content is rendered in a security-sensitive context.</p>
<h2>Conclusion</h2>
<p>This case study demonstrates how a seemingly minor input-handling weakness can introduce serious security risks.</p>
<p>What began as a simple login error parameter allowed us to render arbitrary HTML, manipulate styling, and demonstrate how an application's trusted interface could be abused for credential phishing.</p>
<p>The central issue was not the presence of a query parameter. It was the decision to trust and render its contents as HTML without appropriate safeguards.</p>
<p><strong>A trusted domain does not automatically mean the content displayed on that domain is trustworthy.</strong></p>
<p>For developers, the lesson is to treat untrusted input according to its intended context and avoid unsafe HTML rendering.</p>
<p>For security researchers, the lesson is to assess how an injection affects the application's workflow rather than stopping at the first visible proof of markup rendering.</p>
<p>Sometimes, the difference between an apparently harmless HTML Injection and a serious security finding is understanding how that injection can be abused within a trusted user journey.</p>
<hr />
<h3>Reproduce the Vulnerability</h3>
<p>Explore the intentionally vulnerable application and review the reproduction steps in the Ovawatch Security GitHub repository.</p>
<p><a href="https://github.com/ovawatch-sec/bb-reproduction-labs/tree/main/Labs/bb-002-html-injection-on-err-query-param-lab"><strong>View CloudDesk HTML Injection Lab on GitHub</strong></a></p>
<p><em>This case study is based on a controlled lab reproduction. CloudDesk is a fictional application. No real customer accounts or production systems were used in the published lab.</em></p>
<p><strong>Ovawatch Security — Application Security Research &amp; Penetration Testing</strong></p>
]]></content:encoded></item></channel></rss>