# Sensitive Data Exposure

## Theory

One of the most common misunderstandings for new Solidity developers is assuming that variables marked as `private` or placed inside constructors are hidden from users. This is incorrect: **nothing stored on-chain is confidential**.

Smart contracts store their persistent data inside **EVM storage**, and every full node maintains a complete, publicly accessible copy of it. Because the EVM does not provide built‑in confidentiality, **any value written to storage can be retrieved**, even if Solidity visibility modifiers forbid access from *other contracts*.

This leads to a broad vulnerability class known as **Sensitive Data Exposure**:

* private keys, passwords, salts, API secrets
* access control data
* whitelist lists or hashed values

#### **EVM Memory Model Reminder**

The EVM uses multiple data locations, each with different persistence and visibility rules:

<figure><img src="https://329872044-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMdUKdzuqIuObdvCB3mUR%2Fuploads%2Fgit-blob-f13ccae0e7ef3a820796de1f7b85fdc8c0b01a27%2Fimage.png?alt=media" alt=""><figcaption></figcaption></figure>

| Location     | Lifetime             | Visibility         | Purpose                                |
| ------------ | -------------------- | ------------------ | -------------------------------------- |
| **Storage**  | Permanent, on-chain  | Public (raw bytes) | Contract state                         |
| **Memory**   | Temporary per call   | Internal           | Local variables, arrays                |
| **Stack**    | Per instruction      | Internal           | Expression evaluation                  |
| **Calldata** | Read-only input      | Public             | Function arguments from external calls |
| **Logs**     | Permanent event logs | Public             | Indexed historical data                |

Only **storage** persists between transactions, making it the primary source of Sensitive Data Exposure risks. Although not permanently stored, sensitive information may still appear in transaction calldata, event logs, or deployment bytecode, depending on the contract's implementation.

#### **Variable Visibility Is Not Data Protection**

Solidity visibility modifiers (`public`, `internal`, `private`) are **compile‑time constraints** that restrict how *Solidity code* can access variables.

They do **not**:

* encrypt storage
* prevent external observers from reading storage slots
* hide data from RPC nodes or archive node tools

“Private” only means “other contracts cannot read it through Solidity syntax.”

Thus, the following variable is fully retrievable. “Private” only means “other contracts cannot read it through Solidity syntax.”

```solidity
uint256 private secret;
```

#### Account Storage Layout in Solidity (Technical Overview)

In the Ethereum Virtual Machine (EVM), contract storage is persistent across transactions and organized in **deterministic** **256-bit slots**. This predictability means that if an attacker can reconstruct the layout, they can recover stored values.

Fixed-size variables are stored sequentially starting at slot 0. Solidity applies **tight packing** for variables smaller than 32 bytes, allowing multiple small variables to share the same slot. For example:

```solidity
uint128 a;
uint128 b;
uint256 c;

// Storage layout:
// slot 0 -> contains a and b (packed together)
// slot 1 -> contains c
```

[Mappings](https://docs.soliditylang.org/en/v0.8.30/types.html#mapping-types) behave differently. A mapping declared at slot `p` stores each entry at `keccak256(abi.encode(key, p))`. While mappings are not enumerable, knowing or guessing keys allows an attacker to compute exact storage positions:

```solidity
mapping(address => uint) balances;
// Entry for a key "0x123..." is at keccak256(abi.encode(0x123..., p))
```

Dynamic arrays store their **length in the declared slot** (`p`), with data starting at `keccak256(p)`. Each element `i` is located at `keccak256(p) + i`:

```solidity
uint[] numbers;
// slot p -> length of the array
// slot keccak256(p) + i -> element i
```

Strings and bytes have a dual storage scheme. Values ≤31 bytes are stored directly in the slot. Longer values store a length marker in the slot, with the actual data at `keccak256(p)`:

```solidity
bytes shortData; // ≤31 bytes, stored directly
bytes longData;  // >31 bytes, slot contains length, data at keccak256(p)
```

> Short strings or bytes containing sensitive data are particularly exposed because they reside directly in storage slots.

## Practice

{% tabs %}
{% tab title="Private Variables" %}
**Retrieving a “Private” Variable**

In this contract, `secret` is the first declared state variable, which means it is stored at **slot 0**.

```solidity
contract Vault {
    bytes32 private secret = 0xdeadbeef; // supposed to be hidden
}
```

Anyone can read storage slots directly from an RPC node.\
Since we know `secret` is at slot `0x0`, retrieving it simply means loading that slot. Following foundry script allows it.

```solidity
// forge script scripts/ReadSecret.s.sol --rpc-url $RPC_URL -vv

pragma solidity ^0.8.13;
import "forge-std/Script.sol";

contract ReadSecret is Script {
    function run() external view {
        address target = vm.envAddress("TARGET");
        bytes32 slot0 = vm.load(target, bytes32(uint256(0)));
        console.log("Secret:", uint256(slot0));
    }
}
```

Alternaltively we can use [cast](https://getfoundry.sh/cast/overview) to retreive the data

```bash
cast storage $TARGET 0 --rpc-url $RPC_URL
```

{% endtab %}

{% tab title="Extracting Mapping Entries" %}
**Extracting Mapping Entries**

[Mappings](https://docs.soliditylang.org/en/v0.8.30/types.html#mapping-types) introduce a layer of indirection, but their storage layout is still fully deterministic.\
If an attacker knows the key, they can compute the exact slot where the value is stored.

```solidity
contract Registry {
    mapping(address => uint256) private balances;
}

```

A mapping declared at slot `p` stores each value at:

```
keccak256(abi.encode(key, p))
```

Here `p = 0`, because `balances` is the first variable.

If we want to read the balance for address `k`, we simply compute\
`keccak256(abi.encode(k, 0))` and load that slot. Following foundry script allows it.

```solidity
pragma solidity ^0.8.13;
import "forge-std/Script.sol";

contract ReadMapping is Script {
    function run() external view {
        address target = vm.envAddress("TARGET");
        address user   = vm.envAddress("USER"); // The key (address)

        bytes32 slot = keccak256(abi.encode(user, uint256(0)));
        bytes32 val  = vm.load(target, slot);

        console.log("Balance:", uint256(val));
    }
}
```

This will print the user’s “private” balance. Mappings are not enumerable, but when keys are known (e.g., user addresses), reconstructing entries is trivial.

Alternaltively we can use [cast](https://getfoundry.sh/cast/overview) to retreive the data

```bash
# 1. Compute the storage slot for a given address (user)
cast keccak $(cast abi-encode "(address,uint256)" $USER 0)

# 2. Read the value at that slot (with $SLOT the previous result)
cast storage $TARGET $SLOT --rpc-url $RPC_URL
```

{% endtab %}

{% tab title="Constructor Secrets" %}
**Constructor Secrets Are Public**

A surprisingly common misconception is that constructor arguments are invisible once deployment is complete.\
In reality, **constructor arguments are included in the deployment transaction calldata**, which becomes permanently accessible on-chain. This exposes secrets even before they reach storage.

```solidity
contract APIKeyHolder {
    string private apiKey;

    constructor(string memory key) {
        apiKey = key; // assumed private
    }
}
```

Even though the `apiKey` variable is private and later stored using Solidity’s string layout, the secret **already leaked during deployment**.

// PoC Todo:

1. Retrieve the contract’s creation transaction.
2. Extract the input calldata.
3. Decode it according to:
   * constructor signature
   * ABI encoding rules
4. Read the “secret” directly.

This attack requires no interaction with the contract—just reading public blockchain data.
{% endtab %}
{% endtabs %}

## Resources

{% embed url="<https://en.hackndo.com/sensitive-data/>" %}

{% embed url="<https://scs.owasp.org/SCWE/SCSVS-GOV/SCWE-044/>" %}
