Configuration of Nginx servers against CC attacks

  • 2020-05-10 23:26:33
  • OfStack

The fundamentals of the 0x00 CC attack
The CC attack USES the proxy server to send a large number of URL requests to the website that require a long calculation time, such as database queries, etc., resulting in the server performing a large number of calculations and quickly reaching its own processing capacity to form DOS. However, once an attacker sends a request to the agent, he will actively disconnect the connection, and the agent will not connect to the target server due to the disconnection of the client. Therefore, the resource consumption of the attacker is relatively small, and from the perspective of the target server, the requests from the proxy are all legitimate.
Previous methods to protect against CC attacks
In order to prevent CC, one of the previous methods was to limit the number of connections per IP, which was difficult to achieve in the case of a wide range of addresses. 2 is to restrict the access of the agent, because the agent like 1 will have the field X_FORWARDED_FOR in the HTTP header, but there are limitations, some agents do not have this field in the request, and some clients really need the agent to connect to the target server, this limit will deny some normal users access.
CC attacks are hard to defend against
What makes an CC attack even more terrifying than an DDOS attack is that an CC attack 1 is hard to prevent.
The reasons for personal analysis are as follows:
1. Because the IP from the CC attack are all real and scattered;
2. All packets attacked by CC are normal packets;
3. Requests for CC attacks are all valid requests, requests that cannot be denied.
Guard against CC attack
The effectiveness of CC prevention lies in that the attacker does not accept the data replied by the server and actively disconnects after sending the request. Therefore, to confirm whether the connection is CC, the server does not immediately execute the request command of URL, but simply returns a response with the new URL request address in the response. If it is normal access, the client will take the initiative to reconnect to the redirect page, which is transparent to the user; In the case of the CC attacker, no response data is received, so no reconnection is made, and the server does not need to continue operations.

0x01 validates browser behavior

Simple version of

Let's start with a metaphor.

The community is doing welfare and giving out red envelopes on the square. While the bad guys sent a batch of humanoid robots (no language module) to falsely receive the red envelopes, smart staff need to figure out a way to prevent the envelopes from being falsely received.

So the staff will give the receiver a piece of paper before handing out the red envelope, on which is written "get the red envelope". If the person can read out the words on the paper, he is the person. Give the red envelope. So the robot was seen through and came back in disgrace.

Yes, in this metaphor, the person is the browser, the robot is the attacker, and we can identify them by identifying the cookie function (read the words on the paper). This is the configuration file for nginx.


if ($cookie_say != "hbnl"){
   add_header Set-Cookie "say=hbnl";
   rewrite .* "$scheme://$host$uri" redirect;
}

Let's take a look at the meaning of these lines. When cookie say is empty, give a 302 redirect package that sets cookie say to hbnl. If the visitor can carry the cookie value in the second package, then he can visit the website normally. You can also test 1 with the CC attacker or webbench or directly curl outpacks, both of which live in world 302.

Sure, it's that simple, right? Of course it's not that simple.

Enhanced version

If you look carefully, you will find that the configuration file is still flawed. If the attacker sets cookie to say=hbnl (this can be done on the CC attacker), then the defense is useless. Let's continue with that metaphor.

After the bad man discovered this rule, to each robot installed a loudspeaker, 1 straight repeat "red envelope take, red envelope take", mighty to get the red envelope again.

At this point, the staff's strategy is to ask the recipient to present his/her name on the household register, and read out his/her name, "I am xxx, here is the red envelope." So a group of robots that could only hum "bring the red envelope" were kicked back.

Of course, in order to illustrate the problem, each robot is a household register, was driven back to the reason is not read their own name, although this is a bit absurd, alas.

Then, let's take a look at how configuration files are written this way


if ($cookie_say != "hbnl$remote_addr"){
   add_header Set-Cookie "say=hbnl$remote_addr";
   rewrite .* "$scheme://$host$uri" redirect;
}

The difference between this writing method and the previous one is that the request cookie value of different IP is not the same, for example, IP is 1.2.3.4, then cookie needs to be set is say= hbnl1.2.3.4. Thus, an attacker cannot bypass this restriction by setting up a 1-like cookie(such as the CC attacker). You can continue to test with the CC attacker, and you will find that all the traffic from the CC attacker has entered world 302.

But you can also feel that this is not a foolproof strategy, because once an attacker has studied the mechanics of a website, there is always a way to detect and prefabricate the cookie Settings. Because our differentiated data sources are their own information (IP, user, agent, etc.). An attacker can take the time to create a script that specifically targets a web site.

Perfect version

So how do you get from their own information to something that they can't figure out?

I think you, the smart one, have already guessed that, using salt to hash. For example, md5("opencdn$remote_addr"), although the attacker knows he can IP himself, he cannot figure out how to use his IP to calculate the hash, because he cannot invert the hash. Of course, if you are worried, if cmd5.com 10000 can be detected, you can add some special characters and then scatter them several times.

Unfortunately, nginx does not hash strings by default, so we implemented it with the nginx_lua module.


rewrite_by_lua '
   local say = ngx.md5("opencdn" .. ngx.var.remote_addr)
   if (ngx.var.cookie_say ~= say) then
     ngx.header["Set-Cookie"] = "say=" .. say
     return ngx.redirect(ngx.var.scheme .. "://" .. ngx.var.host .. ngx.var.uri)
   end
';

With this configuration, the attacker cannot calculate the say value in cookie in advance, so the attack traffic (proxy CC and low-level packet CC) is stuck in 302 hell.

As you can see, the logic and the above are 1-mode 1, except that we borrowed the function md5. So if you can, you can do this by installing a third part of nginx's hash calculation, which is probably a little more efficient.

This configuration can be placed in any location. If your website provides API function, it is recommended that API1 should not be included in this section, because the call of API has no browser behavior and will be treated as an attack traffic. Also, some weak 1 point crawlers will be trapped in 302, which should be noted.

At the same time, if you feel that set-cookie seems likely to be simulated by the attacker by parsing the string, you can change the above operation to cookie via header to js via high atmosphere and send back one containing doument.cookie =... Text can be.

So was the attack completely blocked? If the attacker had to add webkit to each attacker at great cost to parse js and execute set-cookie, then he could escape from 302 hell. In nginx's view, the attack traffic is exactly the same as the normal browsing traffic. So how do you defend yourself? The next video will tell you the answer.

0x02 request frequency limit

It has to be said that many CC prevention measures are implemented by limiting the frequency of requests directly. However, many of them have a definite problem.

So what are the problems?

First of all, if I use IP to limit the frequency of requests, it will easily lead to some manslaughter. For example, I only have a few IP in one place, but if I have more than one visitor, the frequency of requests will easily reach the upper limit, and then users in that place will not be able to visit your website.

So you say, well, I'm using SESSION to limit and that's the problem. Well, your SESSION opens one door for attackers. Why is that? You probably already know the general idea, because like the "red envelope" speaker 1, SESSION in many languages or frameworks can be faked. Take PHP for example, you can see PHPSESSIONID in cookie in the browser, ID is different, session is different, and then if you make up an PHPSESSIONID, you will find that the server also recognizes ID, and initializes a session for ID. Thus, an attacker can easily circumvent this request limit on session by simply constructing a new SESSIONID each time the packet is sent.

So how do we do this request frequency limit?

First of all, we need an sessionID that the attacker cannot fabricate. One way is to use a pool to record the ID given each time, and then query it when the request comes. If there is no ID, we will reject the request. We don't recommend this approach. First of all, a website already has an session pool, so it is definitely wasteful to do another one, and you also need to perform traversal comparison queries in the pool, which consumes performance. What we want is an sessionID that can be stateless, ok? You can.


rewrite_by_lua '
   local random = ngx.var.cookie_random
   if(random == nil) then
     random = math.random(999999)
   end
   local token = ngx.md5("opencdn" .. ngx.var.remote_addr .. random)
   if (ngx.var.cookie_token ~= token) then
     ngx.header["Set-Cookie"] = {"token=" .. token, "random=" .. random}
     return ngx.redirect(ngx.var.scheme .. "://" .. ngx.var.host .. ngx.var.uri)
   end
';

Does it sound familiar to you? Yes, this is the perfect version of the configuration in the previous section plus a random number so that a user of IP can have a different token. Similarly, as long as the third module of nginx provides hash and random number functions, this configuration can be done directly in a pure configuration file without lua.

With this token, it is equivalent to having one uncounterfeitable token per visitor, which makes sense to restrict requests.

Thanks to the token setup, we can do it directly through the limit module without whitelisting or blacklisting.


http{
   ...
   limit_req_zone $cookie_token zone=session_limit:3m rate=1r/s;
}

Then we just need to add it at the end of the token configuration above


limit_req zone=session_limit burst=5;

Thus, two more lines of configuration allow nginx to resolve the request frequency limitation at the session layer. However, there seems to be a flaw, because an attacker can break the request frequency limit by getting token through 1, and it would be more perfect if he could limit one IP to get token frequency. Can you do that? You can.


http{
   ...
   limit_req_zone $cookie_token zone=session_limit:3m rate=1r/s;
   limit_req_zone $binary_remote_addr $uri zone=auth_limit:3m rate=1r/m;
}
location /{
   limit_req zone=session_limit burst=5;
   rewrite_by_lua '
   local random = ngx.var.cookie_random
   if (random == nil) then
     return ngx.redirect("/auth?url=" .. ngx.var.request_uri)
   end
   local token = ngx.md5("opencdn" .. ngx.var.remote_addr .. random)
   if (ngx.var.cookie_token ~= token) then
     return ngx.redirect("/auth?url=".. ngx.var.request_uri)
   end
';
}
location /auth {
   limit_req zone=auth_limit burst=1;
   if ($arg_url = "") {
     return403;
   }
   access_by_lua '
     local random = math.random(9999)
     local token = ngx.md5("opencdn" .. ngx.var.remote_addr .. random)
     if (ngx.var.cookie_token ~= token) then
       ngx.header["Set-Cookie"] = {"token=" .. token, "random=" .. random}
       return ngx.redirect(ngx.var.arg_url)
     end
   ';
}

As you might have guessed, this configuration file works by separating the original token function into a single auth page, and then using limit to limit the frequency of the auth page. The frequency here is 1 IP authorizing 1 token per minute. Of course, this quantity can be adjusted according to the business needs.

It is important to note that the auth part I lua USES access_by_lua. The reason is that limit module is executed after rewrite phase. If it is 302 in rewrite phase, limit will fail. Therefore, I can't guarantee that this lua configuration can be implemented with native configuration file, because I don't know how to use configuration file to make 302 jump after rewrite phase, so please give me some advice.

, of course, if you are not satisfied with this limitation, you want to do a IP if reach the upper limit for more than a few times a day after sealing IP directly, it is also possible that you can use the similar way of thinking to do a error page, and then reach the ceiling after 503 but to jump to the error page does not return, also do a request number, then the error page can only access 100 times a day, for example, when more than an error of more than 100 times (request error page 100 times), after the day the IP can no longer access the website.

So, through these configurations we implemented a site access frequency limit. However, such a configuration is not to say that it can completely prevent the attack, can only say that the cost of the attacker becomes higher, so that the site's ability to carry the attack becomes stronger, of course, the premise is that nginx can carry the traffic, and then the bandwidth is not blocked. If your house is blocked and you want to open your door, there is really no way out.

Then, having covered the traffic, let's look at the protection against attacks such as the scanner.

0 x03 scanning

ngx_lua_waf module

This is a nice waf module, and we're not going to repeat the wheel here. This module can be directly used for protection, of course, it can also fully cooperate with the limit module to achieve the effect of sealing IP or session with the above ideas.

0 x04 summary

The purpose of this article is to serve as a starting point, we do not want you to simply copy our examples of the configuration, but to write your own business needs, according to their own site configuration file.


Related articles: