Можно ли предотвратить DoSing в Google App Engine? - PullRequest
17 голосов
/ 05 мая 2009

Я подумываю о разработке приложения для Google App Engine, которое не должно получать слишком много трафика. Я бы предпочел не платить за превышение бесплатных квот. Однако кажется, что было бы довольно легко вызвать атаку типа «отказ в обслуживании», перегружая приложение и превышая квоты. Существуют ли какие-либо методы для предотвращения или затруднения превышения свободных квот? Я знаю, что мог бы, например, ограничить количество запросов от IP (затрудняя превышение квоты ЦП), но есть ли способ усложнить превышение квот запросов или пропускной способности?

Ответы [ 5 ]

16 голосов
/ 05 мая 2009

Нет встроенных инструментов для предотвращения DoS. Если вы пишете Google Apps с использованием Java, тогда вы можете использовать фильтр service.FloodFilter. Следующий фрагмент кода будет выполнен перед выполнением любого из ваших сервлетов.

package service;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;


/**
 * 
 * This filter can protect web server from simple DoS attacks
 * via request flooding.
 * 
 * It can limit a number of simultaneously processing requests
 * from one ip and requests to one page.
 *
 * To use filter add this lines to your web.xml file in a <web-app> section.
 * 
    <filter>
        <filter-name>FloodFilter</filter-name>
        <filter-class>service.FloodFilter</filter-class>
        <init-param>
            <param-name>maxPageRequests</param-name>
            <param-value>50</param-value>
        </init-param>
        <init-param>
            <param-name>maxClientRequests</param-name>
            <param-value>5</param-value>
        </init-param>
        <init-param>
            <param-name>busyPage</param-name>
            <param-value>/busy.html</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>JSP flood filter</filter-name>
        <url-pattern>*.jsp</url-pattern>
    </filter-mapping>
 *  
 *  PARAMETERS
 *  
 *  maxPageRequests:    limits simultaneous requests to every page
 *  maxClientRequests:  limits simultaneous requests from one client (ip)
 *  busyPage:           busy page to send to client if the limit is exceeded
 *                      this page MUST NOT be intercepted by this filter
 *  
 */
public class FloodFilter implements Filter
{
    private Map <String, Integer> pageRequests;
    private Map <String, Integer> clientRequests;

    private ServletContext context;
    private int maxPageRequests = 50;
    private int maxClientRequests = 10;
    private String busyPage = "/busy.html";


    public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException
    {
        String page = null;
        String ip = null;

        try {
            if ( request instanceof HttpServletRequest ) {
                // obtaining client ip and page URI without parameters & jsessionid
                HttpServletRequest req = (HttpServletRequest) request;
                page = req.getRequestURI();

                if ( page.indexOf( ';' ) >= 0 )
                    page = page.substring( 0, page.indexOf( ';' ) );

                ip = req.getRemoteAddr();

                // trying & registering request
                if ( !tryRequest( page, ip ) ) {
                    // too many requests in process (from one client or for this page) 
                    context.log( "Flood denied from "+ip+" on page "+page );
                    page = null;
                    // forwarding to busy page
                    context.getRequestDispatcher( busyPage ).forward( request, response );
                    return;
                }
            }

            // requesting next filter or servlet
            chain.doFilter( request, response );
        } finally {
            if ( page != null )
                // unregistering the request
                releaseRequest( page, ip );
        }
    }


    private synchronized boolean tryRequest( String page, String ip )
    {
        // checking page requests
        Integer pNum = pageRequests.get( page );

        if ( pNum == null )
            pNum = 1;
        else {
            if ( pNum > maxPageRequests )
                return false;

            pNum = pNum + 1;
        }

        // checking client requests
        Integer cNum = clientRequests.get( ip );

        if ( cNum == null )
            cNum = 1;
        else {
            if ( cNum > maxClientRequests )
                return false;

            cNum = cNum + 1;
        }

        pageRequests.put( page, pNum );
        clientRequests.put( ip, cNum );

        return true;
    }


    private synchronized void releaseRequest( String page, String ip )
    {
        // removing page request
        Integer pNum = pageRequests.get( page );

        if ( pNum == null ) return;

        if ( pNum <= 1 )
            pageRequests.remove( page );
        else
            pageRequests.put( page, pNum-1 );

        // removing client request
        Integer cNum = clientRequests.get( ip );

        if ( cNum == null ) return;

        if ( cNum <= 1 )
            clientRequests.remove( ip );
        else
            clientRequests.put( ip, cNum-1 );
    }


    public synchronized void init( FilterConfig config ) throws ServletException
    {
        // configuring filter
        this.context = config.getServletContext();
        pageRequests = new HashMap <String,Integer> ();
        clientRequests = new HashMap <String,Integer> ();
        String s = config.getInitParameter( "maxPageRequests" );

        if ( s != null ) 
            maxPageRequests = Integer.parseInt( s );

        s = config.getInitParameter( "maxClientRequests" );

        if ( s != null ) 
            maxClientRequests = Integer.parseInt( s );

        s = config.getInitParameter( "busyPage" );

        if ( s != null ) 
            busyPage = s;
    }


    public synchronized void destroy()
    {
        pageRequests.clear();
        clientRequests.clear();
    }
}

Если вы используете python, то вам, возможно, придется свернуть свой собственный фильтр.

7 голосов
/ 05 мая 2009

Я не уверен, если это возможно, но Часто задаваемые вопросы по App Engine указывают, что, если вы сможете показать, что это атака DOS, они возместят любые сборы, связанные с атакой.

2 голосов
/ 15 мая 2012

Похоже, что теперь у них есть фильтр на основе IP-адресов, доступный как для Python, так и для Java (я знаю, что это старая ветка, но она все еще высоко оценивается поиском в Google).

https://developers.google.com/appengine/docs/python/config/dos

1 голос
/ 26 августа 2017

Недавно был выпущен GAE firewall , предназначенный для замены предыдущей, довольно ограниченной, DoS Protection Service .

Он поддерживает программные обновления правил брандмауэра через (REST) ​​Admin API: apps.firewall.ingressRules , который может быть объединен с частью логики в приложении для обнаружения DoS, как описано в других ответах , Разница будет заключаться в том, что после развертывания правила на оскорбительные запросы больше не будет взиматься плата, поскольку они больше не доходят до приложения, поэтому сама фильтрация в приложении не требуется.

1 голос
/ 28 июля 2016

Всегда возможно использовать службу, которая предоставляет функции защиты от отказа в обслуживании перед приложением App Engine. Например, Cloudflare предоставляет уважаемую услугу https://www.cloudflare.com/waf/, и другие. Насколько я понимаю (отказ от ответственности: я не пользовался услугой лично), эти функции доступны в бесплатном плане.

Также довольно просто построить реализацию ограничения скорости на основе memcache в самом приложении. Вот первый хит, который я получил от поиска в Google по этому методу: http://blog.simonwillison.net/post/57956846132/ratelimitcache. Этот механизм надежен и может быть экономически эффективным, так как использование общей памяти может быть достаточным и бесплатным. Кроме того, если вы пойдете по этому маршруту, вы сможете контролировать ручки. Недостаток заключается в том, что само приложение должно обрабатывать HTTP-запрос и принимать решение о его разрешении или отклонении, поэтому может потребоваться дополнительная стоимость (или [исчерпание] квоты).

Полное раскрытие. Я работаю в Google на App Engine и не имею отношения к Cloudflare или Саймону Уиллисону.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...