跳转至

HTTP参数污染和JSON注入

服务器端参数污染是指网站在服务器端请求中嵌入用户输入,指向内部 API,且没有充分编码。这意味着攻击者可能能够操控或注入参数,从而使他们能够实现以下行为:

  • 覆盖现有参数。
  • 修改应用行为。
  • 访问未经授权的数据。

你可以测试任何用户输入中任何参数的污染。例如,查询参数、表单字段、头部和 URL 路径参数都可能存在漏洞。

查询字符串中服务器端参数污染的测试

为了测试查询字符串中的服务器端参数污染,可以在输入中加入 # 、 & 和 = 这样的查询语法字符,观察应用程序的响应。

考虑一个漏洞应用,允许你根据用户名搜索其他用户。当你搜索用户时,浏览器会发出以下请求:

Text Only
GET /userSearch?name=peter&back=/home

为了获取用户信息,服务器会通过以下请求查询内部 API:

Text Only
GET /users/search?name=peter&publicProfile=true

截断查询字符串

你可以使用带有 URL 编码的 # 字符来尝试截断服务器端请求。为了帮助你理解回复,你也可以在 # 字符后加字符串。

例如,你可以修改查询字符串为以下内容:

Text Only
GET /userSearch?name=peter%23foo&back=/home

前端将尝试访问以下 URL:

Text Only
GET /users/search?name=peter#foo&publicProfile=true

Note

务必将字符 # 进行 URL 编码。否则,前端应用程序会将它解释为片段标识符,从而无法将其传递给内部 API。

请检查响应内容,以判断查询是否被截断了。例如,如果响应中返回了用户 peter ,那么可能是服务器端查询被截断了。如果返回了 Invalid name 错误信息,说明应用程序可能将 foo 视为用户名的一部分。这表明服务器端请求可能没有被截断。

如果你能够截断服务器端请求,那么就不必将 publicProfile 字段设置为 true 了。这样你就可以利用这个特性返回非公开的用户资料了。

传入了无效的参数

你可以使用 URL 编码的 & 字符来尝试在服务器端请求中添加第二个参数。

例如,你可以将查询字符串修改为如下形式:

Text Only
GET /userSearch?name=peter%26foo=xyz&back=/home

这就会触发以下对内部 API 的服务器端请求:

Text Only
GET /users/search?name=peter&foo=xyz&publicProfile=true

请检查响应内容,以寻找有关如何解析该额外参数的信息。例如,如果响应内容没有发生变化,那就表明该参数已经成功注入,但被应用程序忽略了。

注入有效的参数

如果你能够修改查询字符串,那么就可以尝试在服务器端请求中添加第二个有效的参数。

例如,如果您已经确定了 email 参数,可以像下面这样将其添加到查询字符串中:

Text Only
GET /userSearch?name=peter%26email=foo&back=/home

这就会触发以下对内部 API 的服务器端请求:

Text Only
GET /users/search?name=peter&email=foo&publicProfile=true

覆盖现有参数

为了确认该应用程序是否容易受到服务器端参数污染的问题,你可以尝试覆盖原始参数。方法是在代码中注入另一个具有相同名称的参数。

例如,你可以将查询字符串修改为如下形式:

Text Only
GET /userSearch?name=peter%26name=carlos&back=/home

这就会触发以下对内部 API 的服务器端请求:

Text Only
GET /users/search?name=peter&name=carlos&publicProfile=true

内部 API 解释了两个 name 参数。这一变化会影响应用程序对第二个参数的处理方式。这种情况会因不同的 Web 技术而有所不同。例如:

  • PHP 只会解析最后一个参数。这样一来,用户就会尝试输入形如 carlos 这样的格式。
  • ASP.NET 同时使用了这两种参数。这样一来,用户可能会搜索 peter,carlos ,从而导致出现 Invalid username 错误消息的情况。
  • Node.js/Express 只会解析第一个参数。这样一来,当用户输入 peter 时,系统会输出未经修改的结果。

如果你能够覆盖原始参数,那么你可能能够实施一些利用手段。例如,你可以在请求中添加 name=administrator 这样的参数,这样就能以管理员用户的身份进行登录了。

对 REST 路径中的服务器端参数污染进行检测

RESTful API 允许将参数名称和值放在 URL 路径中,而不是查询字符串中。例如,考虑以下路径:

Text Only
/api/users/123

该 URL 路径可以如下分解:

  • /api 是根 API 端点。
  • /users 表示一个资源,在本例中为 users 。
  • /123 表示一个参数,在这里是指特定用户的标识符。

考虑一个应用程序,它能够根据用户的用户名来编辑用户的个人资料。请求会被发送到以下端点:

Text Only
GET /edit_profile.php?name=peter

这将导致以下服务器端请求:

Text Only
GET /api/private/users/peter

攻击者可能能够操纵服务器端的 URL 路径参数,从而利用该 API 进行攻击。为了检测这种漏洞,可以在 URL 中添加路径遍历序列来修改参数,然后观察应用程序的响应情况。

你可以将 URL 编码后的 peter/../admin 作为 name 参数的值来使用:

Text Only
GET /edit_profile.php?name=peter%2f..%2fadmin

这可能会导致以下服务器端请求:

Text Only
GET /api/private/users/peter/../admin

如果服务器端的客户端或后端 API 将这条路径进行了规范化处理,那么这条路径可能会被解析为 /api/private/users/admin。

HTTP Parameter Pollution (HPP) Overview

Example of HTTP Parameter Pollution (HPP)

A banking application transaction URL:

  • Original URL: https://www.victim.com/send/?from=accountA&to=accountB&amount=10000

By inserting an additional from parameter:

  • Manipulated URL: https://www.victim.com/send/?from=accountA&to=accountB&amount=10000&from=accountC

The transaction may be incorrectly charged to accountC instead of accountA, showcasing the potential of HPP to manipulate transactions or other functionalities such as password resets, 2FA settings, or API key requests.

Technology-Specific Parameter Parsing

  • The way parameters are parsed and prioritized depends on the underlying web technology, affecting how HPP can be exploited.
  • Tools like Wappalyzer help identify these technologies and their parsing behaviors.

PHP and HPP Exploitation

OTP Manipulation Case:

  • Context: A login mechanism requiring a One-Time Password (OTP) was exploited.
  • Method: By intercepting the OTP request using tools like Burp Suite, attackers duplicated the email parameter in the HTTP request.
  • Outcome: The OTP, meant for the initial email, was instead sent to the second email address specified in the manipulated request. This flaw allowed unauthorized access by circumventing the intended security measure.

This scenario highlights a critical oversight in the application's backend, which processed the first email parameter for OTP generation but used the last for delivery.

API Key Manipulation Case:

  • Scenario: An application allows users to update their API key through a profile settings page.
  • Attack Vector: An attacker discovers that by appending an additional api_key parameter to the POST request, they can manipulate the outcome of the API key update function.
  • Technique: Utilizing a tool like Burp Suite, the attacker crafts a request that includes two api_key parameters: one legitimate and one malicious. The server, processing only the last occurrence, updates the API key to the attacker's provided value.
  • Result: The attacker gains control over the victim's API functionality, potentially accessing or modifying private data unauthorizedly.

This example further underscores the necessity for secure parameter handling, especially in features as critical as API key management.

Parameter Parsing: Flask vs. PHP

The way web technologies handle duplicate HTTP parameters varies, affecting their susceptibility to HPP attacks:

  • Flask: Adopts the first parameter value encountered, such as a=1 in a query string a=1&a=2, prioritizing the initial instance over subsequent duplicates.
  • PHP (on Apache HTTP Server): Contrarily, prioritizes the last parameter value, opting for a=2 in the given example. This behavior can inadvertently facilitate HPP exploits by honoring the attacker's manipulated parameter over the original.

Parameter pollution by technology

There results were taken from https://medium.com/@0xAwali/http-parameter-pollution-in-2024-32ec1b810f89

PHP 8.3.11 AND Apache 2.4.62

image-20250320150128643

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

Ruby 3.3.5 and WEBrick 1.8.2

image-20250320150250665

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

Spring MVC 6.0.23 AND Apache Tomcat 10.1.30

image-20250320150312856

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

NodeJS 20.17.0 AND Express 4.21.0

image-20250320150337536

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

GO 1.22.7

image-20250320150406308

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

Python 3.12.6 AND Werkzeug 3.0.4 AND Flask 3.0.3

image-20250320150436432

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

Python 3.12.6 AND Django 4.2.15

image-20250320150500918

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

Python 3.12.6 AND Tornado 6.4.1

image-20250320150528667

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

JSON Injection

Duplicate keys

JSON
obj = {"test": "user", "test": "admin"}

The front-end might believe the first ocurrence while the backend uses the second ocurrence of the key.

Key Collision: Character Truncation and Comments

Certain characters aren't going to be correctly interpreted by the frontend but the backend will interpret them and use those keys, this could be useful to bypass certain restrictions:

JSON
{"test": 1, "test\[raw \x0d byte]": 2}
{"test": 1, "test\ud800": 2}
{"test": 1, "test"": 2}
{"test": 1, "te\st": 2}

Note how in these cases the front end might think that test == 1 and the backend will think that test == 2.

This can also by used to bypass value restrictions like:

JSON
{"role": "administrator\[raw \x0d byte]"}
{"role":"administrator\ud800"}
{"role": "administrator""}
{"role": "admini\strator"}

Using Comment Truncation

JSON
obj = {"description": "Duplicate with comments", "test": 2, "extra": /*, "test": 1, "extra2": */}

Here we will use the serializer from each parser to view its respective output.

Serializer 1 (e.g., GoLang's GoJay library) will produce:

  • description = "Duplicate with comments"
  • test = 2
  • extra = ""

Serializer 2 (e.g., Java's JSON-iterator library) will produce:

  • description = "Duplicate with comments"
  • extra = "/*"
  • extra2 = "*/"
  • test = 1

Alternatively, straightforward use of comments can also be effective:

JSON
obj = {"description": "Comment support", "test": 1, "extra": "a"/*, "test": 2, "extra2": "b"*/}

Java’s GSON library:

JSON
{ "description": "Comment support", "test": 1, "extra": "a" }

Ruby’s simdjson library:

JSON
{ "description": "Comment support", "test": 2, "extra": "a", "extra2": "b" }

Inconsistent Precedence: Deserialization vs. Serialization

JSON
obj = {"test": 1, "test": 2}

obj["test"] // 1
obj.toString() // {"test": 2}

Float and Integer

The number

Text Only
999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999

can be decoded to multiple representations, including:

Text Only
999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
9.999999999999999e95
1E+96
0
9223372036854775807

Which might create inconsistences