HTTP Parameter Pollution in 2024 !

Mahmoud M. Awali
11 min readSep 28, 2024

--

Hi after going through all the Black Hat and DEFCON web security researches in 2024 , I noticed that the easiest way to break web apps is by understanding how components handle your requests so in this write-up , I’m going to explain how languages and frameworks handle our parameters .

💬 JUST❗

I didn’t intend to see how languages and frameworks handle our parameters but since my setup requires a back-end I gave it some time .

In the beginning , I started to see which delimiter is being used to split our parameter .

⚠️ Ruby

It was known to me that Ruby uses the & and ; delimiters to split parameters .

1️⃣ PHP 8.3.11 AND Apache 2.4.62

<!DOCTYPE html>
<html>
<body>
<?php
echo $_GET['name'];
?>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<?php
echo $_POST['name'];
?>
</body>
</html>

1️⃣.1️⃣ Ignore anything after %00 in the parameter name .

Ignore anything after %00 in the parameter name .

1️⃣.2️⃣ Handle name[]=one as array .

1️⃣.3️⃣ handle name=one&name=two as name=two .

1️⃣.4️⃣ handle name=one&name[]=two as array .

1️⃣.5️⃣ handle name[]=one&name=two as name=two .

1️⃣.6️⃣ handle name[]=one&name[]=two as array .

1️⃣.7️⃣ handle GET Request name=one AND body name=two as name=one .

1️⃣.8️⃣ handle POST Request name=two AND body name=one using _GET as name=two .

1️⃣.9️⃣ handle POST Request name=two AND body name=one using _POST as name=one .

💬 Our PHP Parameter Pollution Summary in 2024❗

2️⃣ Ruby 3.3.5 AND WEBrick 1.8.2

require 'webrick'

def getPOC(query_param)
"<html><body>#{query_param}</body></html>"
end

server = WEBrick::HTTPServer.new(Port: 8000)

server.mount_proc '/' do |req, res|
param = req.query['name']
result = getPOC(param)
res['Content-Type'] = 'text/html'
res.body = result
end

trap 'INT' do server.shutdown end
server.start

2️⃣.1️⃣ Not Recognized name[]=one .

2️⃣.2️⃣ handle name=one&name=two as name=one .

1️⃣.3️⃣ handle name=one&name[]=two as name=one .

1️⃣.4️⃣ handle name[]=one&name=two as name=two .

1️⃣.5️⃣ Not Recognized name[]=one&name[]=two .

1️⃣.6️⃣ handle GET Request name=one AND body name=two as name=one .

1️⃣.7️⃣ handle POST Request name=two AND body name=one as name=one .

💬 Our Ruby Parameter Pollution Summary in 2024❗

3️⃣ Spring MVC 6.0.23 AND Apache Tomcat 10.1.30

@GetMapping("/getMapping")
@ResponseBody
String getMapping(@RequestParam(required = false, name = "name") String name) {

return "<!DOCTYPE html><html><body>" + name + "</body></html>";
}

@PostMapping("/postMapping")
@ResponseBody
String postMapping(@RequestParam(required = false, name = "name") String name) {

return "<!DOCTYPE html><html><body>" + name + "</body></html>";
}

@RequestMapping("/requestMapping")
@ResponseBody
String requestMapping(@RequestParam(required = false, name = "name") String name) {

return "<!DOCTYPE html><html><body>" + name + "</body></html>";
}

3️⃣.1️⃣ handle name[]=one :-
GetMapping as status=400
PostMapping as one
GET RequestMapping as status=400
POST RequestMapping as one

3️⃣.2️⃣ handle name=one&name=two :-
GetMapping as one,two
PostMapping as one,two
GET RequestMapping as one,two
POST RequestMapping one,two

3️⃣.3️⃣ handle name=one&name[]=two :-
GetMapping as status=400
PostMapping as one
GET RequestMapping as status=400
POST RequestMapping as one

3️⃣.4️⃣ handle name[]=one&name=two :-
GetMapping as status=400
PostMapping as two
GET RequestMapping as status=400
POST RequestMapping as two

3️⃣.5️⃣ handle name[]=one&name[]=two :-
GetMapping as status=400
PostMapping as one,two
GET RequestMapping as status=400
POST RequestMapping as one,two

3️⃣.6️⃣ handle query name=one AND body name=two without Content-Type :-
GetMapping as one
PostMapping as status=405
GET RequestMapping as one
POST RequestMapping as one

3️⃣.7️⃣ handle query name=two AND body name=one with Content-Type :-
GetMapping as status=405
PostMapping as two,one
GET RequestMapping as two
POST RequestMapping as two,one

💬 Our Spring MVC Parameter Pollution Summary in 2024❗

4️⃣ NodeJS 20.17.0 AND Express 4.21.0

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


app.get('/', (request, response) => {

const nameInput = request.query.name;

var html = `
<!DOCTYPE html>
<html>
<body>${nameInput}</body>
</html>
`;

response.send(html);
});

app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = 3000;

app.use(bodyParser.urlencoded({ extended: true }));

app.post('/', (request, response) => {
const nameInput = request.body.name;

var html = `
<!DOCTYPE html>
<html>
<body>${nameInput}</body>
</html>
`;

response.send(html);
});

app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});

4️⃣.1️⃣ handle name[]=one as one .

4️⃣.2️⃣ handle name=one&name=two as one,two .

4️⃣.3️⃣ handle name=one&name[]=two as one,two .

4️⃣.4️⃣ handle name[]=one&name=two as one,two .

4️⃣.5️⃣ handle name[]=one&name[]=two as one,two .

4️⃣.6️⃣ handle GET Request name=one AND body name=two as name=one .

4️⃣.7️⃣ handle POST Request name=two AND body name=one as name=one .

💬 Our NodeJS Parameter Pollution Summary in 2024❗

5️⃣ GO 1.22.7

package main

import (
"html/template"
"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {

//r.ParseForm()
//nameValue := r.Form.Get("name")
nameValue := r.URL.Query().Get("name")

var tmpl = `<!DOCTYPE html><html><body>` + nameValue + `</body></html>`

t := template.New("main")
t, _ = t.Parse(tmpl)
t.Execute(w, "Hello")
}

func main() {
server := http.Server{
Addr: "0.0.0.0:8000",
}
http.HandleFunc("/", handler)
server.ListenAndServe()
}

5️⃣.1️⃣ Not Recognized name[]=one .

5️⃣.2️⃣ handle name=one&name=two as name=one .

5️⃣.3️⃣ handle name=one&name[]=two as name=one .

5️⃣.4️⃣ handle name[]=one&name=two as name=two .

5️⃣.5️⃣ Not Recognized name[]=one&name[]=two .

5️⃣.6️⃣ handle GET Request name=one AND body name=two as name=one .

5️⃣.7️⃣ handle POST Request name=two AND body name=one as name=one .

💬 Our GO Parameter Pollution Summary in 2024❗

6️⃣ Python 3.12.6 AND Werkzeug 3.0.4 AND Flask 3.0.3

from flask import request
from flask import Flask

app = Flask(__name__)

@app.route('/',methods=['GET'])
def index():
getName = "<!DOCTYPE html><html><body>" + request.args.get('name') + "</body></html>"
return getName

if __name__ == '__main__':
app.run(debug=True)
from flask import request
from flask import Flask

app = Flask(__name__)

@app.route('/',methods=['POST'])
def index():
getName = "<!DOCTYPE html><html><body>" + request.form.get('name') + "</body></html>"
return getName

if __name__ == '__main__':
app.run(debug=True)

6️⃣.1️⃣ Not Recognized name[]=one .

6️⃣.2️⃣ handle name=one&name=two as name=one .

6️⃣.3️⃣ handle name=one&name[]=two as name=one .

6️⃣.4️⃣ handle name[]=one&name=two as name=two .

6️⃣.5️⃣ Not Recognized name[]=one&name[]=two .

6️⃣.6️⃣ handle GET Request name=one AND body name=two as name=one .

6️⃣.7️⃣ handle POST Request name=two AND body name=one as name=one .

💬 Our Flask Parameter Pollution Summary in 2024❗

7️⃣ Python 3.12.6 AND Django 4.2.15

from django.http import HttpResponse
from django.shortcuts import render

def index(request):
getName = "<!DOCTYPE html><html><body>" + request.GET.get('name') + "</body></html>"
return HttpResponse(getName)
from django.http import HttpResponse
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def index(request):
getName = "<!DOCTYPE html><html><body>" + request.POST.get('name') + "</body></html>"
return HttpResponse(getName)

7️⃣.1️⃣ Not Recognized name[]=one .

7️⃣.2️⃣ handle name=one&name=two as name=two .

7️⃣.3️⃣ handle name=one&name[]=two as name=one .

7️⃣.4️⃣ handle name[]=one&name=two as name=two .

7️⃣.5️⃣ Not Recognized name[]=one&name[]=two .

7️⃣.6️⃣ handle GET Request name=one AND body name=two as name=one .

7️⃣.7️⃣ handle POST Request name=two AND body name=one as name=one .

💬 Our Django Parameter Pollution Summary in 2024❗

8️⃣ Python 3.12.6 AND Tornado 6.4.1

import asyncio
import tornado

class MainHandler(tornado.web.RequestHandler):
def get(self):
getName = "<!DOCTYPE html><html><body>" + self.get_query_argument('name', default=None) + "</body></html>"
self.write(getName)

def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])

async def main():
app = make_app()
app.listen(8000)
await asyncio.Event().wait()

if __name__ == "__main__":
asyncio.run(main())
import asyncio
import tornado

class MainHandler(tornado.web.RequestHandler):
def post(self):
getName = "<!DOCTYPE html><html><body>" + self.get_body_argument('name', default=None) + "</body></html>"
self.write(getName)

def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])

async def main():
app = make_app()
app.listen(8000)
await asyncio.Event().wait()

if __name__ == "__main__":
asyncio.run(main())

8️⃣.1️⃣ Not Recognized name[]=one .

8️⃣.2️⃣ handle name=one&name=two as name=two .

8️⃣.3️⃣ handle name=one&name[]=two as name=one .

8️⃣.4️⃣ handle name[]=one&name=two as name=two .

8️⃣.5️⃣ Not Recognized name[]=one&name[]=two .

8️⃣.6️⃣ handle GET Request name=one AND body name=two as name=one .

8️⃣.7️⃣ handle POST Request name=two AND body name=one as name=one .

💬 Our Tornado Parameter Pollution Summary in 2024❗

📝🧵 KEYPOINTS :-

— — — — — — — — — — — — — — — — — — — — — — — — — — —

1️⃣ PHP 8.3.11 AND Apache 2.4.62

1️⃣.1️⃣ Ignore anything after %00 in the parameter name .
1️⃣.2️⃣ Handle name[] as array .
1️⃣.3️⃣ _GET not meaning GET Method .
1️⃣.4️⃣ Prefer the last parameter .

2️⃣ Ruby 3.3.5 AND WEBrick 1.8.2

2️⃣.1️⃣ Uses the & and ; delimiters to split parameters .
2️⃣.2️⃣ Not Recognized name[] .
2️⃣.3️⃣ Prefer the first parameter .

3️⃣ Spring MVC 6.0.23 AND Apache Tomcat 10.1.30

3️⃣.1️⃣ POST RequestMapping == PostMapping & GET RequestMapping == GetMapping .
3️⃣.2️⃣ POST RequestMapping & PostMapping Recognized name[] .
3️⃣.3️⃣ Prefer name if name AND name[] existing .
3️⃣.4️⃣ Concatenate parameters e.g. first,last .
3️⃣.5️⃣ POST RequestMapping & PostMapping Recognized query parameter with Content-Type .

4️⃣ NodeJS 20.17.0 AND Express 4.21.0

4️⃣.1️⃣ Recognized name[] .
4️⃣.2️⃣ Concatenate parameters e.g. first,last .

5️⃣ GO 1.22.7

5️⃣.1️⃣ NOT Recognized name[] .
5️⃣.2️⃣ Prefer the first parameter .

6️⃣ Python 3.12.6 AND Werkzeug 3.0.4 AND Flask 3.0.3

6️⃣.1️⃣ NOT Recognized name[] .
6️⃣.2️⃣ Prefer the first parameter .

7️⃣ Python 3.12.6 AND Django 4.2.15

7️⃣.1️⃣ NOT Recognized name[] .
7️⃣.2️⃣ Prefer the last parameter .

8️⃣ Python 3.12.6 AND Tornado 6.4.1

8️⃣.1️⃣ NOT Recognized name[] .
8️⃣.2️⃣ Prefer the last parameter .

💬 Thanks

𝕏 0xAwali

--

--

Responses (9)