Note that this blog post was updated on the 21st of November to address issues
in the original fuzz.
In this blog post I am going to help you get started with fuzzing browsers as fast as possible. The goal is to be up and running with minimal thought and effort. It'll be quick and dirty, using publicly available tools to save time.
You will be able to fuzz all browsers common on the Windows Desktop: Google Chrome, Microsoft Edge, Mozilla Firefox, or Microsoft Internet Explorer. I may have exagerate a little when I said sixty seconds in the title but it'll be less than sixty minutes for sure.
I will show you how to create a script that can fuzz a browser continuously. It will generate HTML files and load then in a browser to detect any crashes while they are rendered. For each unique issue detected, a report will be generated that contains information about the type of crash, location and its potential security impact. It will also save the HTML files that triggered the crash, so you can reproduce the issue later for further manual analysis. If the browser survives loading all generated HTML files it will be terminated automatically. After all this the script will start over, generating new files and testing them continuously.
To generate HTML files, I'll be using Domato. Domato is a Python script that can generate HTML files for the express purpose of triggering security issues while rendering them. It is written and maintained by Ivan Fratric of Google Project Zero.
To run the browser and detect crashes, I'll be using BugId. BugId is a Python script that can run an application in a debugger to detect and analyze crashes and recognize security issues. It is written and maintained by myself.
Our target browser will be any the four browsers most commonly used on Windows: Google Chrome, Microsoft Edge, Mozilla Firefox, or Microsoft Internet Explorer. We will be using the stock browser with as little changes in the configuration as possible to guarantee that any issue we find will actually affect users of these browsers and not just our special build or configuration.
I will assume you have a freshly installed copy of Windows 10 running in a VM
without any software installed. Both Domato and BugId are written in Python
2, so we need to download that. At the time of this writing, the download
page for Python was available at https://www.
Next up is Debugging Tools for Windows from Microsoft. This is used by
BugId to run the browser in a debugger. It comes as part of the Windows 10
Software Development Kit (SDK), which you can download from
https://developer.
Finally we need to get Domato and BugId. You do not need to install these; you
can simply download them and then run them directly. We will create a folder
C:\Fuzzing
in which we'll store everything.
You can download the most recent version of Domato
from https://github.C:\Fuzzing
. You should now have a folder
C:\Fuzzing\domato-master
which contains generate.
among many other files.
You can download the most recent release version of BugId from
https://github.BugId.####-##-##.zip
and not the source code;
BugId has some dependencies that are included in the release zip but not the
source code zip. Create a folder C:\Fuzzing\BugId
and unzip the downloaded
file there. You should now have a file C:\Fuzzing\BugId\BugId.
among the
files in that folder.
You may want to install Google Chrome and/or Mozilla Firefox if you
want to fuzz them; Microsoft Edge and Internet Explorer come installed
by default. If you are installing Firefox on a 64-bit version of Windows, you
may want to make sure you download the 64-bit version of Firefox from
https://www.
Many security issues cause corruption of data inside a browser process. This
may not result in a crash immediately if the browser does not use that data
until much later. To increase the chances of detecting such issues, BugId uses
a feature called page-heap. This needs to be enabled manually for each
binary that is part of a browser, which requires administrator privileges.
BugId comes with a script that can do this so you don't have to worry about
the details. Open a cmd.C:\Fuzzing\BugId
and run any of the following commands to enable page-heap in various browsers:
PageHeap. cmd edge ON
PageHeap. cmd chrome ON
PageHeap. cmd firefox ON
PageHeap. cmd msie ON
You do not need to enable it for all browsers if you are not planning to fuzz them but it does not hurt either. The output should look like this:
Note that BugId itself does not require Administrator privileges; just
PageHeap.
We should now have everything we need on the machine to start fuzzing. Let's make sure everything is working by running the following commands:
"c:\Python27\python. exe" -c "print 'ok!'"
"c:\Python27\python. exe" "domato-master\generator. py"
"BugId\BugId. cmd" "%WinDir%\system32\cmd. exe" --cBugId. bEnsurePageHeap=false -- /C ECHO ok!
The output should look like this:
We'll create the batch script C:\Fuzzing\Fuzz.
to do our fuzzing. This
batch script will continously loop through commands to run Domato to generate
HTML files and run BugId to test them in the browser. If a crash is detected,
it will save the generated HTML files and crash report in a separate folder for
later manual analysis. You can download it [here][(xxx). It consists of four
sections.
The first section of our batch script contains some variables that allow us to configure fuzzing:
@ECHO OFF
SET BASE_FOLDER=C:\Fuzzing
SET PYTHON_EXE=C:\Python27\python. exe
:: What browser do we want to fuzz? ("chrome" | "edge" | "firefox" | "msie")
SET TARGET_BROWSER=edge
:: How many HTML files shall we teach during each loop?
SET NUMBER_OF_FILES=100
:: How long does it take BugId to start the browser and load an HTML file?
SET BROWSER_LOAD_TIMEOUT_IN_SECONDS=30
:: How long does it take the browser to render each HTML file?
SET AVERAGE_PAGE_LOAD_TIME_IN_SECONDS=2
:: Optionally configurable
SET BUGID_FOLDER=%BASE_FOLDER%\BugId
SET DOMATO_FOLDER=%BASE_FOLDER%\domato-master
SET TESTS_FOLDER=%BASE_FOLDER%\Tests
SET REPORT_FOLDER=%BASE_FOLDER%\Report
SET RESULT_FOLDER=%BASE_FOLDER%\Results
:: Store our results in a folder named after the target:
IF NOT EXIST "%RESULT_FOLDER%\%TARGET_BROWSER%" MKDIR "%RESULT_FOLDER%\%TARGET_BROWSER%"
This should all be reasonably self explanatory. You may want to change the
browser you are targetting and modify the timeout and average page load time
based on your experience, as they depend greatly on the hardware you are
running on. Tests are generated in the TESTS_FOLDER
folder and if we find
any crashes, we store information about them in the RESULT_FOLDER
folder.
Next we'll add the main loop to our batch script:
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Repeatedly generate tests and run them in the browser.
:LOOP
CALL :GENERATE
IF ERRORLEVEL 1 EXIT /B 1
CALL :TEST
IF ERRORLEVEL 1 EXIT /B 1
GOTO :LOOP
In the :GENERATE
label we'll ask Domato to generate test HTML files in the
"Tests" folder.
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Generate test HTML files
:GENERATE
REM Delete old files.
DEL "%TESTS_FOLDER%\fuzz-*.html" /Q >nul 2>nul
REM Generate new HTML files.
"%PYTHON_EXE%" "%DOMATO_FOLDER%\generator. py" --output_dir "%TESTS_FOLDER%" --no_of_files %NUMBER_OF_FILES%
IF ERRORLEVEL 1 EXIT /B 1
EXIT /B 0
In the :TEST
label we'll start the browser in BugId and get it to load the
test HTML files. BugId will exit when the application crashes, or has ran
without crashing for a given number of seconds. When can check BugId's exit
code to determine what happened and then look for the HTML report file:
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Run browser in BugId and load test HTML files
:TEST
REM Delete old report if any.
IF NOT EXIST "%REPORT_FOLDER%" (
MKDIR "%REPORT_FOLDER%"
) ELSE (
DEL "%REPORT_FOLDER%\*.html" /Q >nul 2>nul
)
REM Guess how long the browser needs to run to process all tests.
REM This is used by BugId to terminate the browser in case it survives all tests.
SET /A MAX_BROWSER_RUN_TIME=%BROWSER_LOAD_TIMEOUT_IN_SECONDS% + %AVERAGE_PAGE_LOAD_TIME_IN_SECONDS% * %NUMBER_OF_FILES%
REM Start browser in BugId...
"%PYTHON_EXE%" "%BUGID_FOLDER%\BugId. py" "%TARGET_BROWSER%" "--sReportFolderPath=\"%REPORT_FOLDER:\=\\%\"" --nApplicationMaxRunTime=%MAX_BROWSER_RUN_TIME% -- "file://%TESTS_FOLDER%\index. html"
IF ERRORLEVEL 2 (
ECHO - ERROR %ERRORLEVEL%.
REM ERRORLEVEL 2+ means something went wrong.
ECHO Please fix the issue before continuing...
EXIT /B 1
) ELSE IF NOT ERRORLEVEL 1 (
EXIT /B 0
)
ECHO Crash detected!
REM Create results sub-folder based on report file name and copy test files
REM and report.
FOR %%I IN ("%REPORT_FOLDER%\*.html") DO (
CALL :COPY_TO_UNIQUE_CRASH_FOLDER "%RESULT_FOLDER%\%%~nxI"
EXIT /B 0
)
ECHO BugId reported finding a crash, but not report file could be found!?
EXIT /B 1
Finally, in the :COPY_TO_UNIQUE_CRASH_FOLDER
label we will copy the HTML
report, which has a name that is unique to the bug, to a folder with the same
name. We will also copy the tests files that triggered the crash to the "Repro"
sub-folder. If BugId detect the same issue during a previous fuzzing run, the
folder already exists and the result will be ignored.
:COPY_TO_UNIQUE_CRASH_FOLDER
SET REPORT_FILE=%~nx1
REM We want to remove the ".html" extension from the report file name to get
REM a unique folder name:
SET UNIQUE_CRASH_FOLDER=%RESULT_FOLDER%\%TARGET_BROWSER%\%REPORT_FILE:~0,-5%
IF EXIST "%UNIQUE_CRASH_FOLDER%" (
ECHO Repro and report already saved after previous test detected the same issue.
EXIT /B 0
)
ECHO Copying report and repro to %UNIQUE_CRASH_FOLDER% folder...
REM Move report to unique folder
MKDIR "%UNIQUE_CRASH_FOLDER%"
MOVE "%REPORT_FOLDER%\%REPORT_FILE%" "%UNIQUE_CRASH_FOLDER%\report. html"
REM Copy repro
MKDIR "%UNIQUE_CRASH_FOLDER%\Repro"
COPY "%TESTS_FOLDER%\*.html" "%UNIQUE_CRASH_FOLDER%\Repro"
ECHO Report and repro copied to %UNIQUE_CRASH_FOLDER% folder.
EXIT /B 0
This completes our fuzzing script fuzz.
. You can download the complete
script here.
Domato simply generates a bunch of HTML files. In order to get the browser to
load them all in sequence, we'll create a separate HTML file in
C:\Fuzzing\Tests\index.
. Here's an example of an HTML file that will load
the tests generated by Domato in iframes and give each one 5 seconds to finish
loading. Some pages may load a lot faster, others will time out. After letting
it run a few times, I've set AVERAGE_PAGE_LOAD_TIME_IN_SECONDS
to 2 in the
script above as that seems to be a reasonable average.
<!doctype html>
<!-- saved from url=(0014)about:internet -->
<html>
<head>
<script>
var oIFrameElement = document. getElementById("IFrame"),
nPageLoadTimeoutInSeconds = 5,
uIndex = 0;
onload = function fLoadNext() {
var sIndex = "" + uIndex++;
while (sIndex. length < 5) sIndex = "0" + sIndex;
var sTestURL = "fuzz-" + sIndex + ".html";
document. title = "Loading test " + sTestURL + "...";
// Add iframe element that loads the next test case.
var oIFrame = document. body. appendChild(document. createElement("iframe")),
bFinished = false;
oIFrame. setAttribute("sandbox", "allow-scripts");
oIFrame. setAttribute("src", sTestURL);
// Hook load event handler and add timeout to remove the iframe when the test is finished.
try {
oIFrame. contentWindow. addEventListener("load", fCleanupAndLoadNext);
} catch (e) {
// This may cause an exception because some browsers treat different files loaded from the
// local file system as comming from different origins.
};
var xTimeout = setTimeout(fCleanupAndLoadNext, nPageLoadTimeoutInSeconds * 1000);
function fCleanupAndLoadNext() {
// Both the load event and the timeout can call this function; make sure we only execute once:
if (!bFinished) {
bFinished = true;
console. log("Finished test " + sTestURL + "...");
// Let's give the page another 5 seconds to render animations etc.
setTimeout(function() {
// Remove the iframe from the document to delete the test.
try {
document. body. removeChild(oIFrame);
} catch (e) {};
}, 5000);
fLoadNext();
};
};
};
</script>
</head>
<body>
</body>
</html>
You can download this file here. (Note: this file
was updated on April 2nd, 2019 to add a try ... catch
-block around the
addEventListener
call to prevent an exception in certain browsers.)
That's it! You are now ready to start fuzzing. Simply run the script we created and watch it generate HTML files and load them in the browser over and over...
fuzz. cmd
Once a crash is found, a folder is created by BugId that contains a HTML report with details about the type of crash and the location in the code where the crash occured, along with some other useful information. Don't worry if you do not understand all the details in this report. The most important part from a security standpoint is in the top section, after the heading "Security impact". If this says "Potentially exploitable security issue", you have most likely found a security issue. If this says "Denial of service" it is most likely just a regular bug with no serious security implications. You are likely to find a lot of Access Violation while attempting to Read memory using a NULL pointer, which will show up as AVR@NULL. This is a common bug, but not a security issue. Other common bugs that are not security issues include OOM (Out Of Memory), Assert (The application noticed something was not as expected and terminated itself), *RecursiveCall (a function calls itself over and over until it runs out of stack memory), and IntegerDivideByZero (the application tried to divide by zero).
If you do find security security issue, it is likely to be reported as RAF/WAF (Read/Write After Free; the application thought it was done using a chunk of memory and freed it but continued to use it afterwards) or OOBR/OOBW (Out Of Bounds Read/Write; the application try to read/write beyond the bounds of a memory chunk).
Please report all security issues to the browser vendor as soon as possible. If you don't know exactly what to report, zipping the report and repro files in the results folder and sending it to the vendor should provide them with enough information to reproduce, analyze and fix the issue. They may even decide to give you a nice bug bounty as a thank you for helping them secure their users!
You can report security issues in the following locations:
The best thing about fuzzing is that you get to let machines do all the hard
work. The more machines you use, the bigger your chances of finding bugs. It
is quite easy to use the script created in this blog post to fuzz on multiple
machines at the same time and save results in a single location. All we need to
do is create a shared folder on a server to store the results and change the
RESULT_FOLDER
in our script to point to this network share. This allows you
to run the script on as many machines as you want and collect have all of them
report the crashes they find to a single folder. For example:
SET RESULT_FOLDER=\\server\Fuzzing\Results
If you are going to run this script on multiple machines, you may want to have these machines automatically log-in as a particular user and start the script whenever they are started. You can do that by executing the following commands in an Administrator command prompt:
SET USERNAME=<Username>
SET PASSWORD=<Password>
SET WINLOGON=HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon
SET RUN=HKLM\Software\Microsoft\Windows\CurrentVersion\Run
REG ADD "%WINLOGON%" /v DefaultUserName /t REG_SZ /d "%USERNAME%" /f >nul
REG ADD "%WINLOGON%" /v DefaultPassword /t REG_SZ /d "%PASSWORD%" /f >nul
REG ADD "%WINLOGON%" /v AutoAdminLogon /t REG_SZ /d 1 /f >nul
REG ADD "%RUN%" /v Fuzz /d "\"%COMSPEC%\" /K \"C:\Fuzzing\fuzz. cmd\"" /f >nul
Note that you need to modify these command to provide the username and password for your machine. When you are done, you can test if it works by rebooting the machine: it should automatically log in and start fuzzing.
The above set-up will get you started quickly, but not very efficiently. In a future blog post I will show you how to improve on this basic concept.
Also, we are using a stock public fuzzer, which has been run extensively on many, many machines for a very long time. This means your chances of finding a vulnerability are small. But you can improve this fuzzer by adding to the "Grammar" it uses to generate tests and start looking for bugs in new features where no-one may have looked before.
If you want to create your own fuzzers or improve Domato, have a look at the files it generates. It should give you a good idea of the kind of tests you will want to generate yourself in order to find bugs.
For now, I wish you happy fuzzing and good luck finding vulnerabilities!
fuzz.