Why real findings matter more than theoretical risks

Security whitepapers love to talk about abstract threat models. CTOs want to know something different: what will you actually find in my system, and is it worth the money? The answer depends on the system, but after dozens of engagements with Nigerian fintechs - payment processors, lending platforms, mobile money apps, digital banks - the patterns are remarkably consistent.

Every finding below is from an actual Simpa Labs engagement. Client names and product details are anonymized. The vulnerabilities, the exploitation methods, and the impact assessments are real. We have organized them by severity: critical findings first, because those are the ones that keep CTOs awake at night.

Critical findings: immediate exploitation, direct financial impact

Critical findings are vulnerabilities an attacker can exploit right now, with no special access or unusual conditions required. They result in direct financial loss, mass data exposure, or complete system compromise.

Finding 1: admin account takeover via IDOR

What it was: The platform's API used sequential integer IDs to identify user accounts. The API endpoint for retrieving user profile data - including account balances, transaction history, and KYC documents - accepted a user ID as a URL parameter. No authorization check verified that the requesting user owned the account being accessed.

How we found it: During authenticated API testing, we modified the user ID in the GET request from our test account's ID to a sequential ID. The API returned the full profile of a different user. We incremented the ID across a range and confirmed access to 47,000 customer accounts, including several admin accounts. With the admin account data, we authenticated as a platform administrator with full system access.

Potential impact: Complete access to 47,000 customer accounts, including BVN data, transaction histories, and wallet balances. An attacker with admin access could initiate transfers, modify account data, or export the entire customer database. Estimated exposure: ₦200 million+ in customer funds, plus NDPA penalties of up to 2% of annual revenue for the mass PII exposure.

How the client fixed it: Implemented server-side authorization checks on every API endpoint that accesses user-specific data. Replaced sequential integer IDs with UUIDs. Added automated tests to verify authorization on all resource-access endpoints. For the technical implementation, see our guide on fixing broken object-level authorization.

Finding 2: payment amount manipulation

What it was: The checkout API accepted the payment amount as a client-supplied parameter. The server-side processing did not validate the amount against the actual order total or price catalogue. An attacker could modify the amount field in the API request to any value.

How we found it: We intercepted the checkout API request using a proxy, changed the amount field from ₦50,000 (the actual product price) to ₦1, and forwarded the request. The transaction processed successfully. The customer received the product. The merchant received ₦1.

Potential impact: Any customer could purchase any product or service at any price they chose. At the client's transaction volume of approximately 2,000 daily transactions, an attacker automating this exploit could drain inventory or services while paying effectively nothing. Estimated daily exposure: ₦35 million+ in potential revenue loss.

How the client fixed it: Moved amount calculation entirely server-side. The checkout API now accepts only a cart or order ID - the server retrieves the correct amount from the database and passes it to the payment processor. Client-supplied amount fields are ignored entirely. Added server-side validation that the payment processor's confirmed amount matches the order total before marking the transaction as successful.

Finding 3: production database accessible from the public internet

What it was: The production MongoDB instance was running on a cloud server with the default port (27017) open to all inbound connections. MongoDB was configured with no authentication - no username, no password, no access control of any kind.

How we found it: During infrastructure reconnaissance, a port scan of the client's IP range revealed port 27017 responding. We connected using the mongo shell with no credentials and had full read/write access to the production database. The database contained 200,000+ customer records including full names, phone numbers, BVN data, home addresses, and KYC document references.

Potential impact: Complete data breach of 200,000+ customer records containing BVN data - one of the most sensitive data types under Nigerian data protection law. An attacker could exfiltrate the entire database, modify transaction records, delete data, or deploy ransomware. NDPA penalty exposure alone: ₦10 million minimum or 2% of annual revenue, plus individual lawsuits from affected customers.

How the client fixed it: Enabled MongoDB authentication with strong credentials. Restricted network access to the database server - only the application servers can connect, all other inbound connections on port 27017 are dropped at the firewall. Enabled encryption at rest and in transit. Implemented database activity monitoring. Rotated all application credentials that had been stored alongside the database connection string.

Finding 4: hardcoded AWS secret keys in mobile app JavaScript bundle

What it was: The client's React Native mobile app contained AWS access key and secret key credentials embedded directly in the JavaScript bundle. The credentials had IAM permissions for S3 (full access) and RDS (read/write).

How we found it: We downloaded the APK from the Google Play Store, extracted the JavaScript bundle, and searched for common credential patterns. The AWS credentials appeared as string constants in the configuration module. We validated the credentials against the AWS API - they were active and had the permissions described. See our methodology in reverse engineering Android fintech apps.

Potential impact: Full access to the S3 buckets containing KYC documents (passport scans, utility bills, selfie verification images) for all customers. Read/write access to the RDS production database. An attacker could exfiltrate customer data, modify financial records, or delete database contents. The credentials were retrievable by anyone who downloaded the app - approximately 150,000 installs.

How the client fixed it: Immediately rotated the compromised AWS credentials. Removed all secrets from the mobile app codebase. Implemented a backend proxy - the mobile app now authenticates to the client's API, which makes AWS calls server-side using credentials stored in AWS Secrets Manager. Added automated secrets scanning to the CI/CD pipeline to prevent future credential commits. See our detailed guide on where to store API keys and secrets.

Want to know what a pentest would find in your system?

Get a Scoping Call

High findings: significant impact, exploitable with moderate effort

High findings require some additional effort, specific conditions, or chaining with other weaknesses to exploit fully. They carry significant impact and should be remediated within one week of discovery.

Finding 5: JWT tokens that never expire

What it was: The authentication system issued JWT tokens with no expiration claim. Once issued, a token remained valid indefinitely - there was no token refresh mechanism and no server-side revocation capability.

How we found it: We decoded the JWT payload and confirmed the absence of the "exp" claim. We then tested a token issued during the engagement against the API 72 hours later - it still worked. We checked whether password changes, logout actions, or account deactivation invalidated existing tokens - none of them did.

Potential impact: A stolen JWT token - via XSS, man-in-the-middle, or device theft - grants permanent access to the victim's account. The account holder has no way to revoke access, even by changing their password. For a fintech processing financial transactions, this means permanent unauthorized access to a customer's wallet, transaction capabilities, and personal data. Read more in our analysis of JWT token security mistakes.

How the client fixed it: Added a 15-minute expiration to access tokens. Implemented a refresh token mechanism with 7-day expiry and single-use rotation. Added a server-side token blocklist for immediate revocation on logout, password change, and account deactivation.

Finding 6: sequential and predictable password reset tokens

What it was: Password reset tokens were generated using a predictable pattern based on the current Unix timestamp. By requesting two reset tokens in quick succession and observing the pattern, an attacker could predict the reset token for any user's account.

How we found it: We requested password resets for two test accounts within one second of each other and compared the tokens received via email. The tokens differed by a predictable increment. We generated a predicted token for a third account and successfully used it to reset the password.

Potential impact: Full account takeover for any user on the platform. An attacker requests a password reset for the target account, predicts the token, and sets a new password - all without access to the victim's email. At 47,000 active users, every account was vulnerable.

How the client fixed it: Replaced the timestamp-based token generation with cryptographically secure random tokens (32 bytes, hex-encoded). Added a 15-minute expiration and single-use enforcement. Implemented rate limiting on the password reset endpoint.

Finding 7: publicly readable S3 bucket containing KYC documents

What it was: An S3 bucket storing customer KYC documents - passport scans, national ID photos, utility bills, selfie verification images - had its access control list (ACL) set to public-read. Anyone with the bucket URL could list and download all objects.

How we found it: During infrastructure enumeration, we identified the S3 bucket name from API responses that included pre-signed URLs. We tested the bucket with unauthenticated requests and confirmed public list and read access to over 85,000 documents.

Potential impact: Exposure of identity documents for 85,000+ customers. These documents enable identity theft, fraudulent account creation at other financial institutions, and SIM swap attacks. The NDPA implications for a breach of this scale involving government-issued identity documents would be severe - regulatory penalties plus individual claims from affected customers.

How the client fixed it: Removed public access immediately. Implemented bucket policies restricting access to the application's IAM role only. Enabled S3 Block Public Access at the account level to prevent future misconfigurations. Migrated to pre-signed URLs with 5-minute expiration for legitimate document access.

Finding 8: no rate limiting on login - brute force succeeds in 60 seconds

What it was: The login endpoint accepted unlimited authentication attempts with no rate limiting, account lockout, or CAPTCHA challenge. The platform used four-digit PINs as the primary authentication factor.

How we found it: We wrote a script that submitted all 10,000 possible four-digit PIN combinations for a test account. The correct PIN was found in 47 seconds. The account showed no lockout, and no alert was generated.

Potential impact: Any customer account could be compromised in under 60 seconds. At the platform's user base of 30,000 accounts, an automated attack could compromise hundreds of accounts per hour. Combined with the IDOR vulnerability (Finding 1), an attacker could enumerate valid accounts and brute-force each one systematically. See our detailed coverage of rate limiting for payment APIs.

How the client fixed it: Implemented progressive rate limiting: after 3 failed attempts, a 30-second delay; after 5, a 5-minute lockout with CAPTCHA required; after 10, account lockout requiring customer support intervention. Added failed login alerting. Migrated from four-digit PINs to six-digit PINs with mandatory password as a second factor for high-value transactions.

Finding 9: CORS misconfiguration allowing any origin

What it was: The API's Cross-Origin Resource Sharing (CORS) configuration reflected whatever origin the request included in the Origin header, and included credentials in the response. This meant any website on the internet could make authenticated requests to the API on behalf of a logged-in user.

How we found it: We sent API requests with arbitrary Origin headers and observed that the response always echoed the Origin back in the Access-Control-Allow-Origin header, with Access-Control-Allow-Credentials set to true.

Potential impact: An attacker creates a malicious website and tricks a logged-in user into visiting it. The malicious site makes authenticated API calls as the victim - reading account data, initiating transactions, or modifying profile information. This is effectively a cross-site request forgery bypass that works against every authenticated endpoint.

How the client fixed it: Replaced the reflective CORS configuration with a whitelist of allowed origins - only the production domain and the staging domain are permitted. Removed wildcard credential support. Added SameSite cookie attributes as an additional layer of CSRF protection.

Medium findings: exploitable under specific conditions

Finding 10: verbose error messages leaking database structure

What it was: API error responses in production included full PostgreSQL error messages containing table names, column names, data types, and constraint names. Some errors included partial SQL queries.

How we found it: Sending malformed input to API endpoints triggered detailed database errors in the JSON response. For example, a type mismatch on a payment endpoint returned: "ERROR: invalid input syntax for type uuid: 'test' - column users.bvn_number varchar(11)".

Potential impact: Database structure disclosure gives an attacker a detailed map of the data model - table names, column names, data types, and relationships. This information dramatically reduces the effort required for SQL injection attacks and targeted data exfiltration. The error message above reveals that BVN data is stored in a column called bvn_number in the users table, confirming both the presence and location of sensitive PII.

How the client fixed it: Implemented a global error handler that returns generic error messages to API consumers in production. Detailed errors are logged server-side for debugging but never exposed in API responses. Added different error handling configurations for development and production environments.

Finding 11: user enumeration via password reset

What it was: The password reset endpoint returned different HTTP response codes and messages for valid versus invalid email addresses. A valid email returned 200 with "Reset email sent." An invalid email returned 404 with "User not found."

How we found it: We submitted password reset requests for known valid and invalid email addresses and compared the responses. The timing difference was also measurable - valid email requests took 200-400ms (sending the email), while invalid requests returned in under 50ms.

Potential impact: An attacker can enumerate every valid email address on the platform. Combined with credential stuffing (testing leaked passwords from other breaches) or brute-force attacks, user enumeration is the first step in targeted account compromise.

How the client fixed it: Unified the response - all password reset requests now return 200 with "If an account with that email exists, a reset link has been sent." Response timing is normalised using a fixed delay regardless of whether the email exists.

Finding 12: missing Content-Security-Policy enabling XSS

What it was: The web application had no Content-Security-Policy (CSP) header. Combined with insufficient input sanitization on user-generated content fields, this enabled stored cross-site scripting (XSS) attacks.

How we found it: We injected a JavaScript payload into a user profile field (the "business name" field accepted HTML). The payload executed in the browsers of other users who viewed the profile, including admin users viewing customer accounts through the back-office interface.

Potential impact: Stored XSS executing in admin browsers could steal admin session tokens, enabling full admin account takeover. In a fintech context, this means access to all customer data, transaction controls, and platform configuration.

How the client fixed it: Implemented a strict Content-Security-Policy header blocking inline scripts and restricting script sources. Added input sanitization on all user-generated content fields. Encoded output in all templates.

Finding 13: session tokens not invalidated on password change

What it was: When a user changed their password, existing session tokens remained valid. An attacker who had stolen a session token would retain access even after the account owner changed their password.

How we found it: We authenticated, captured the session token, changed the account password from a different session, and then tested the original token - it still worked.

Potential impact: Password changes - often the first action a user takes when they suspect account compromise - do not actually revoke unauthorized access. The attacker retains access until the session expires naturally, which in this case was never (see Finding 5).

How the client fixed it: Implemented session invalidation on password change - all existing sessions for the account are revoked, and the user must re-authenticate. Added session invalidation on email change and MFA enrollment changes as well.

Low findings: security hygiene gaps

Finding 14: missing security headers

What it was: The application was missing several recommended HTTP security headers: X-Frame-Options (allowing clickjacking), X-Content-Type-Options (allowing MIME sniffing), and Referrer-Policy (leaking URLs to third parties).

How we found it: Standard header analysis during the reconnaissance phase. These are checked automatically as part of every engagement.

Potential impact: Individual impact is low. Clickjacking could be used to trick users into performing unintended actions. MIME sniffing could enable certain content injection attacks. Referrer leakage could expose sensitive URL parameters to third-party services.

How the client fixed it: Added all recommended security headers via middleware configuration. This is typically a 15-minute fix that improves the security baseline of the entire application.

Finding 15: TLS 1.0 and 1.1 still enabled

What it was: The server accepted connections using TLS 1.0 and TLS 1.1, both of which have known vulnerabilities and have been deprecated by all major browser vendors and standards bodies since 2020. TLS 1.2 and 1.3 were also supported.

How we found it: TLS version scanning during the infrastructure assessment phase. The server negotiated TLS 1.0 when the client requested it.

Potential impact: TLS 1.0 and 1.1 are vulnerable to BEAST, POODLE, and other protocol-level attacks that can enable interception of encrypted traffic under specific conditions. For PCI DSS compliance, TLS 1.0 has been explicitly prohibited since June 2018. Continued support could impact the client's PCI DSS compliance status.

How the client fixed it: Disabled TLS 1.0 and 1.1 at the load balancer level. Configured minimum TLS version to 1.2. Verified that no legitimate clients were using the deprecated versions before disabling.

Key pattern

Findings chain together

The most dangerous scenario is not any single finding in isolation - it is the chain. User enumeration (Finding 11) feeds brute-force attacks (Finding 8), which grant access to accounts where session tokens never expire (Finding 5), on a platform where IDOR gives access to any other account (Finding 1). Each individual finding has a severity rating. The chain of findings has a combined impact that far exceeds the sum of its parts.

What these findings cost to fix versus what they cost to ignore

Every finding above was fixed by the client's engineering team within one to four weeks of the pentest report delivery. The total remediation effort across a typical engagement is 40-80 engineering hours - roughly one to two sprints of focused work.

The cost of ignoring them is measured differently. The NDPA can impose penalties of ₦10 million or 2% of annual gross revenue. NIBSS reported ₦53.4 billion in confirmed fraud losses across the Nigerian financial sector. The Flutterwave unauthorized transactions alone exceeded ₦11 billion in 2024. Against those numbers, a ₦3-5 million pentest that finds and fixes these vulnerabilities is not an expense - it is the cheapest insurance available.

If you want to understand what a pentest would find in your system, the only way to know is to run one. See how a Simpa Labs pentest works for the full engagement process.

Ready to find out what is in your system?

Book a Pentest

Related reading

Blog: How a Simpa Labs pentest works · What happens after a pentest · BOLA vulnerabilities in payment APIs · Top vulnerabilities in Nigerian companies

Guides: Pentest report explained · Vulnerability assessment vs pentest · How to book a pentest

Services: Penetration testing · API security testing · Authentication security

Frequently asked questions

Are these real pentest findings?

Yes. Every finding described in this article comes from an actual Simpa Labs penetration testing engagement with a Nigerian fintech. Client names, specific product details, and identifying information have been anonymized, but the vulnerabilities, discovery methods, and remediation steps are real.

How many findings does a typical pentest produce?

A typical penetration test on a Nigerian fintech with 30-80 API endpoints produces between 15 and 40 findings across all severity levels. The distribution usually includes 1-3 critical findings, 3-7 high findings, 5-15 medium findings, and 5-10 low findings. The exact numbers depend on the maturity of the codebase and the security practices already in place.

Will a pentest find all vulnerabilities?

No security assessment can guarantee 100% coverage. A penetration test finds vulnerabilities within the defined scope during the engagement window. The goal is to identify the highest-impact vulnerabilities that would cause the most damage if exploited. Regular testing - annually at minimum, quarterly for high-risk fintechs - increases coverage over time.

What is the difference between critical and high severity findings?

Critical findings are immediately exploitable with direct financial impact or mass data exposure - an attacker can use them right now to steal money or access thousands of customer records. High findings require more effort or specific conditions to exploit but still carry significant impact. Critical findings should be fixed within 24-48 hours; high findings within one week.