Demystifying HTTP Parameters for beginners

4 months ago 32
BOOK THIS SPACE FOR AD
ARTICLE AD

In this blog, I am going to assume that you are aware of the terms FQDNs, URLs, endpoints, and query parameters. I’ll be introducing the basics of parameters.

The Traditional Form Tag

The method attribute of the <form> tag controls which type of HTTP request to send.

<form method=”post”></form>

GET is the default setting. The action attribute is related to the src attribute of <img>. Like the src attribute, action makes a request to the specified resource.

<form action=”/endpoint”></form>GET /endpoint HTTP/1.2

… omitted

Query Parameters vs Body Parameters

Consider an html code snippet based on the form tag (post request):

<form action=”/login” method=”post”>
<label for=”uname”> username </label>
<input type=”text” id=”uname” name=”username” />
<label for=”pass”> password </label>
<input type=”password” id=”pass” name=”password” />
<button>Submit</button>
</form>

This would generate:

POST /login HTTP/1.2
Host: localhost:3000
Content-Type: text/html
Accept-Language: en-US,en;q=0.5

… omitted

username=&password=

Form elements like <input> and <button> tags with the name attribute embedded in the form tag are read as parameters when the form is submitted. For the POST method, these parameters are included in the request body and are called body parameters. For the GET method, these parameters are appended to the URL as query parameters.

Let’s revisit the html code snippet but replace the method to “get” instead of “post”:

<form action=”/login”>
<label for=”uname”> username </label>
<input type=”text” id=”uname” name=”username” />
<label for=”pass”> password </label>
<input type=”password” id=”pass” name=”password” />
<button>Submit</button>
</form>

This would generate:

GET /login?username=&password= HTTP/1.2
Host: localhost:3000
Content-Type: text/html
Accept-Language: en-US,en;q=0.5

… omitted

We are now left with a query string of username and password

Parameter Parsing

Now let’s see some codes of how the backend parses these parameters (in Express):

… top omitted

// Route handler/controller for the /login endpoint

app.post(‘/login’, (req, res) => {
console.log(req.body.username) // outputs username parsed from body
console.log(req.body.password) // outputs password parsed from body
})

As you can see, we have a route handler set up for /login in handling POST requests. In the code, you can see that the program takes in the request object. We accessed the request object’s body and the attributes of the body which contains username and password. The output should output the user’s values set for the parameters in the request.

From a get request perspective, we would be parsing from the URL to obtain the query parameters instead of the request body:

… top omitted

// Route handler/controller for the /login endpoint

app.get(‘/login’, (req, res) => {
console.log(req.query.username) // outputs username parsed from url
console.log(req.query.password) // outputs password parsed from url
})

Other Parameter Formats

We have seen basic query parameters and body parameters, but we must go over REST (Representational Stat Transfer) URLs. REST endpoints are usually named by nouns and usually display data in a json format. For example, /users. With REST endpoints, they are commonly set up as what is known as route parameters.

Backend:

… top omitted

// Route handlers/controllers for the /users/{user_id} endpoint

app.get(‘/users/:userId’, (req, res) => {
console.log(req.params.userId) // outputs Id
})

app.post('/users', (req, res) => {
console.log(req.body.name) // outputs name
}),

GET Request :

GET /users/1 HTTP/1.2
Host: localhost:3000
Content-Type: text/html
Accept-Language: en-US,en;q=0.5

… omitted

This would return data from the user with the user id of 1. In this example, you can see that the syntax of the route parameter is /users/{user_id} in a directory-like syntax thus getting its name “route” or even “path” parameters.

POST Request:


POST /users HTTP/1.2
Host: localhost:3000
Content-Type: application/json
Accept-Language: en-US,en;q=0.5
Content-Length: 12

… omitted

}
name:""
}

This would create a new user and user id. As you can see, the POST request is in a json format and you would still parse from the body.

Static vs Dynamic Parameters

Static parameters are values that will not change. They are a set of hard-coded fixed values rather than arbitrary user-controllable inputs. However, this does not mean that there isn’t functionality, hidden params, or even vulnerabilities.
/products?category=electronics

Dynamic parameters are values that can be changed based on the context. They are usually user-controllable or controlled by the server.
/products/:productId

Unused/Ignored Query Parameters

These are usually query parameters that have been left hanging in the URL without a use, what I like to call, “dead” or “dud” parameters. One way this can happen is when the developer mishandles the traditional form submission functionality.
/app/home?isLoggedIn=True

Depending on the scenario, the above query parameter may not be used by the application to determine/verify your auth state. So, assuming this is an unused/ignored parameter, changing the parameter isLoggedIn to False will not impact your login state while navigating through the web app.

Consider this example:

Assume you have a frontend that follows the traditional form tag submission blended in with javascript to submit a separate request to the backend endpoint.

Frontend:

<!-- Form Setup -->
<form id=”loginForm” onsubmit=”login()”>
<label for=”uname”> username </label>
<input type=”text” id=”uname” name=”username” />
<label for=”pass”> password </label>
<input type=”password” id=”pass” name=”password” />
<button>Submit</button>
</form>

<!-- Logic to retrieve form data -->
<script>
async function login() {
const form = document.getElementById('loginForm');
const formData = new FormData(form);
const data = {
username: formData.get('username'),
password: formData.get('password')
};

const url = 'https://backend.example.com/login';

// logic to send a POST request to the backend endpoint
await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
credentials: 'include'
});
}
</script>

Code explanation:

Upon submission, a new form data object is created and takes in the form data in a structured way, allowing you to access and manipulate the form data. The data gets placed in the fetch() function and sends a POST request to the backend endpoint, /login.

Backend:

app.post(‘/login’, (req, res) => {
console.log(req.body.username)
console.log(req.body.password)
});

In the backend, you can see the route handler / controller for the /login endpoint.

The request would look like this:

POST /login HTTP/1.2
Host: localhost:3000
Content-Type: text/html
Accept-Language: en-US,en;q=0.5

… omitted

username=&password=

But however, recall what happens when you submit a form that’s used by the traditional form tag without a set method attribute. That’s right, it would take values from the name attribute from the input tag and automatically perform a GET request. So, in the browser’s URL search bar, we now have:
https://example.com/login?username=&password=

Not only did it get reflected in the browser’s URL, but it was sent in the form of a POST request as well. The reason why this is considered unusable is the idea that changing the value of the query params of https://example.com/login?username=&password= like https://example.com/login?username123=&password=123 will not do anything as the web application will not parse from it. Recall that the javascript only accepts submitted form data and will make a POST request based off that, leaving these query params useless.

Reflecting form input in the URL can be stopped with the preventDefault() method.

Query Parameters with Multiple Entry Points

Recall from the last example on unused query parameters. In that case, the only entry point for the user-controllable input would be from a form submission. However, let’s assume that on page load or DOM content loaded, the javascript would parse the query parameters ?username=&password= from https://example.com/login as well. Then, the value of those query parameters would be used in a POST request to the backend endpoint, /login.

So now, we have multiple entry points — parsing of the query params from the URL and the form submission. This means that the webpage of the form takes in input from query parameters (the url) and input from the form submission provided from the value of the input tags.

With this, it opens a possible attack vector for CSRF attacks including CSDT2CSRF (Client-Side Directory Traversal to Cross-Site Request Forgery):
https://www.erasec.be/blog/client-side-path-manipulation/

Linkedin: https://www.linkedin.com/in/nathan-w-76ba78202/

Read Entire Article