lenin.alevski | July 1st, 2019
Companies dream of building apps that run without errors and give their users the freedom to do whatever they want. This requires removing validations, data-type constraints, and accepting various user inputs and formats to represent the same information, all while ensuring the application won’t crash and burn.
So, how does this work? There’s a step during the software development lifecycle dedicated to ensuring product quality, which means developers need to write tests (unit testing, integration testing, manual testing, etc.) that ensure the application won’t crash in unexpected situations. Writing these kinds of tests is sometimes difficult, because we need to anticipate any scenario in which users might interact with an application. This is where Fuzzing helps detect flaws in application logic.
The first step to fuzzing an application begins with identifying all the ways a user can input information to the service. Tools like Postman simplify this task, but also having access to service endpoint documentation is even better!
It’s clear how users can send information to the service by using the headers and parameters in the HTTP request.
This service already has some logic in place for handling errors. It displays nice, informative messages instead of an ugly, and potentially risky (because of an information disclosure), backtrace derived from an unhandled exception.
class NetsuiteProxyService < Sinatra::Base
set :show_exceptions, false
...
...
...
get "/get_roles" do
content_type :json
roles = client.list_select_values_for(
record_type: 'employee',
sublist: 'rolesList',
field: 'selectedRole',
page_index: 1
)
return {
'roles' => roles
}.to_json
end
error do
content_type :json
status 400
{
error: env['sinatra.error'].message
}.to_json
end
end
At this point, it’s possible the development team already had some tests in place in order to check for the Happy, Sad, and Bad paths. These tests may return between two hundred and four hundred status responses, but never five hundred. However, the goal of Fuzzing is to go beyond that—we want to throw as much garbage as possible at the service and see what happens or breaks.
GET /get_roles HTTP/1.1
Host: localhost:3000
NS-account: account-id
NS-consumer_key: consumer-key
NS-consumer_secret: consumer-secret
NS-token_id: token-id
NS-token_secret: token-secret
NS-email: example@email.com
NS-password: supersecretpassword
NS-API_VERSION: 2
NS-WSDL_DOMAIN: example.com
NS-WSDL: example.com/wsdl
User-Agent: PostmanRuntime/7.15.0
Accept: */*
Cache-Control: no-cache
Postman-Token: f7567113-0ee5-4410-ba21-0be8397895f3,2a1632b5-3c6b-4ef2-b56d-bd3b513da4fa
Host: localhost:3000
cookie: session=COOKIE
accept-encoding: gzip, deflate
Connection: keep-alive
cache-control: no-cache
We do this by writing a simple python script using the HTTP request generated by Postman (or whatever other tool) and a FOR LOOP, on each iteration we change the values for the headers and parameters using different types of payloads.
Choosing the Payloads
The type of payloads we use during the application fuzzing is important. I recommend downloading SecLists. SecList is a compilation of lists that include different usernames, passwords, SQL injection and XSS payloads, known path and files, etc.
The Fuzzing section contains many lists with the most horrendous (and evil) payloads you can feed the application.
Automatic Fuzzing with Burp Suite
You can do all of this manually, but it’s sometimes necessary to Fuzz hundreds of endpoints. That’s simply too much work. If faced with that scenario, we can automate the Fuzzing process with a tool like Burp Suite, specifically using the Intruder module.
In Burp Suite, we can choose which parts of the requests we want to “rotate” using different payloads. By default, Burp Suite suggests some of them like parameters and the session cookie, but we can add or delete existing ones.
Next, we need to choose which payload lists to use. We can use the ones we download from the SecList repository. In this example, I’m using SQLi and XSS Polyglots.
Once everything is configured, we can start the intruder attack. Burp Suite will send garbage to our service endpoint, showing the request headers/parameters and the obtained response.
A quick way to see if something went wrong is to look at the response status and length. An abnormally long length indicates some form of information disclosure from the server. In this example, there are three payloads that crashed the service, specifically on the NS-WSDL header.
NS-WSDL: SLEEP(1)%20%2f%2aâ%20or%20SLEEP(1)%20or%20ââ%20or%20SLEEP(1)%20or%20â%2a%2f
NS-WSDL: IF(SUBSTR(@@version,1,1)%3c5,BENCHMARK(2000000,SHA1(0xDE7EC71F1)),SLEEP(1))%2f%2a'XOR(IF(SUBSTR(@@version,1,1)%3c5,BENCHMARK(2000000,SHA1(0xDE7EC71F1)),SLEEP(1)))OR'%7c%22XOR(IF(SUBSTR(@@version,1,1)%3c5,BENCHMARK(2000000,SHA1(0xDE7EC71F1)),âSLEEP(1)))OR%22%2a%2f
NS-WSDL: â%20onclick%3dalert(1)%2f%2f%3cbutton%20â%20onclick%3dalert(1)%2f%2f%3e%20%2a%2f%20alert(1)%2f%2f
This backtrace can disclose details about the server environment path where the application and usernames exist. We can take a look at the service log to confirm the application crashed and learn that the error occurred within the function that handles the errors, which is ironic.
This seems to be an issue with encoding, specifically in how Ruby converts objects (Hashes) to JSON format. We proceed to create a fix for this issue and now the error no longer occurs.
error do
content_type :json
message = env['sinatra.error'].message || ''
status 400
{
error: message.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
}.to_json
end
Final thoughts
Fuzzing is commonly associated with penetration testing and exploit development, but it’s a testing technique that—if used correctly—can help development teams deliver more robust (less crashes) applications while helping the company identify potential vulnerabilities at early stages.
Happy hacking!